From e1332c8010705047f68407becc313e5978288f1a Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Wed, 8 Mar 2023 14:36:43 -0600 Subject: [PATCH 01/72] port `DefaultGridSampler` --- src/aztec/DetectorTest.rs | 10 ++- src/common/bit_matrix.rs | 4 + src/common/default_grid_sampler.rs | 126 ++++++++++++---------------- src/common/grid_sampler.rs | 74 +++++++++++++++- src/common/perspective_transform.rs | 25 ++++-- src/rxing_result_point.rs | 6 ++ 6 files changed, 164 insertions(+), 81 deletions(-) diff --git a/src/aztec/DetectorTest.rs b/src/aztec/DetectorTest.rs index cd93c31..c3074cc 100644 --- a/src/aztec/DetectorTest.rs +++ b/src/aztec/DetectorTest.rs @@ -115,12 +115,18 @@ fn test_error_in_parameter_locator(data: &str) { // dbg!(copy.to_string()); // dbg!(make_larger(©, 3).to_string()); // The detector doesn't seem to work when matrix bits are only 1x1. So magnify. + // dbg!(isMirror, error1, error2); let r = Detector::new(&make_larger(©, 3)).detect(isMirror); - assert!(r.is_ok()); + assert!( + r.is_ok(), + "decode should be ok: {isMirror:?}, {error1}, {error2}" + ); let r = r.expect("result already tested as ok"); assert_eq!(r.getNbLayers(), layers); assert_eq!(r.isCompact(), compact); - let res = decoder::decode(&r).expect("decode should be ok"); + let res = decoder::decode(&r).expect(&format!( + "decode should be ok: {isMirror}, {error1}, {error2}" + )); assert_eq!(data, res.getText()); } } diff --git a/src/common/bit_matrix.rs b/src/common/bit_matrix.rs index 5232b2a..f55ccfe 100644 --- a/src/common/bit_matrix.rs +++ b/src/common/bit_matrix.rs @@ -695,6 +695,10 @@ impl BitMatrix { new_bm } + pub fn is_in(&self, p: Point) -> bool { + self.isIn(p, 0) + } + pub fn isIn(&self, p: Point, b: i32) -> bool { b as f32 <= p.x && p.x < self.getWidth() as f32 - b as f32 diff --git a/src/common/default_grid_sampler.rs b/src/common/default_grid_sampler.rs index 94964d7..96ec72a 100644 --- a/src/common/default_grid_sampler.rs +++ b/src/common/default_grid_sampler.rs @@ -19,9 +19,9 @@ // import com.google.zxing.NotFoundException; use crate::common::Result; -use crate::{Exceptions, Point}; +use crate::{point, Exceptions, Point}; -use super::{BitMatrix, GridSampler, PerspectiveTransform, Quadrilateral, SamplerControl}; +use super::{BitMatrix, GridSampler, SamplerControl}; /** * @author Sean Owen @@ -30,24 +30,6 @@ use super::{BitMatrix, GridSampler, PerspectiveTransform, Quadrilateral, Sampler pub struct DefaultGridSampler; impl GridSampler for DefaultGridSampler { - fn sample_grid_detailed( - &self, - image: &BitMatrix, - dimensionX: u32, - dimensionY: u32, - dst: Quadrilateral, - src: Quadrilateral, - ) -> Result { - let transform = PerspectiveTransform::quadrilateralToQuadrilateral(dst, src)?; - - self.sample_grid( - image, - dimensionX, - dimensionY, - &[SamplerControl::new(dimensionX, dimensionY, transform)], - ) - } - fn sample_grid( &self, image: &BitMatrix, @@ -55,62 +37,64 @@ impl GridSampler for DefaultGridSampler { dimensionY: u32, controls: &[SamplerControl], ) -> Result { - if dimensionX == 0 || dimensionY == 0 { + if dimensionX <= 0 || dimensionY <= 0 { return Err(Exceptions::NOT_FOUND); } - let mut bits = BitMatrix::new(dimensionX, dimensionY)?; - let mut points = vec![Point::default(); dimensionX as usize]; - for y in 0..dimensionY { - // for (int y = 0; y < dimensionY; y++) { - let max = points.len(); - let i_value = y as f32 + 0.5; - let mut x = 0; - while x < max { - // for (int x = 0; x < max; x += 2) { - points[x].x = (x as f32) + 0.5; - points[x].y = i_value; - x += 1; - } - controls - .first() - .unwrap() - .transform - .transform_points_single(&mut points); - // Quick check to see if points transformed to something inside the image; - // sufficient to check the endpoints - self.checkAndNudgePoints(image, &mut points)?; - // try { - let mut x = 0; - while x < max { - // for (int x = 0; x < max; x += 2) { - // if points[x] as u32 >= image.getWidth() || points[x + 1] as u32 >= image.getHeight() - // { - // return Err(Exceptions::notFound( - // "index out of bounds, see documentation in file for explanation".to_owned(), - // )); - // } - if image - .try_get(points[x].x as u32, points[x].y as u32) - .ok_or(Exceptions::not_found_with( - "index out of bounds, see documentation in file for explanation", - ))? - { - // Black(-ish) pixel - bits.set(x as u32, y); + + for SamplerControl { p0, p1, transform } in controls { + // To deal with remaining examples (see #251 and #267) of "numercial instabilities" that have not been + // prevented with the Quadrilateral.h:IsConvex() check, we check for all boundary points of the grid to + // be inside. + let isInside = |p: Point| -> bool { + return image.is_in(transform.transform_point(p.centered())); + }; + for y in (p0.y as i32)..(p1.y as i32) { + // for (int y = y0; y < y1; ++y) + if !isInside(point(p0.x, y as f32)) || !isInside(point(p1.x - 1.0, y as f32)) { + return Err(Exceptions::NOT_FOUND); + } + } + for x in (p0.x as i32)..(p1.x as i32) { + // for (int x = x0; x < x1; ++x) + if !isInside(point(x as f32, p0.y)) || !isInside(point(x as f32, p1.y - 1.0)) { + return Err(Exceptions::NOT_FOUND); } - x += 1; } - // } catch (ArrayIndexOutOfBoundsException aioobe) { - // // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting - // // transform gets "twisted" such that it maps a straight line of points to a set of points - // // whose endpoints are in bounds, but others are not. There is probably some mathematical - // // way to detect this about the transformation that I don't know yet. - // // This results in an ugly runtime exception despite our clever checks above -- can't have - // // that. We could check each point's coordinates but that feels duplicative. We settle for - // // catching and wrapping ArrayIndexOutOfBoundsException. - // throw NotFoundException.getNotFoundInstance(); - // } } + + let mut bits = BitMatrix::new(dimensionX, dimensionY)?; + for SamplerControl { p0, p1, transform } in controls { + // for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) { + for y in (p0.y as i32)..(p1.y as i32) { + // for (int y = y0; y < y1; ++y) + for x in (p0.x as i32)..(p1.x as i32) { + // for (int x = x0; x < x1; ++x) { + let p = transform.transform_point(Point::from((x, y)).centered()); //mod2Pix(centered(PointI{x, y})); + + if image.get_point(p) { + bits.set(x as u32, y as u32); + } + } + } + } + + // dbg!(image.to_string()); + // dbg!(bits.to_string()); + + let projectCorner = |p: Point| -> Point { + for SamplerControl { p0, p1, transform } in controls { + if p0.x <= p.x && p.x <= p1.x && p0.y <= p.y && p.y <= p1.y { + return transform.transform_point(p) + point(0.5, 0.5); + } + } + Point::default() + }; + + let _tl = projectCorner(point(0.0, 0.0)); + let _tr = projectCorner(Point::from((dimensionX, 0))); + let _bl = projectCorner(Point::from((dimensionX, dimensionY))); + let _br = projectCorner(Point::from((0, dimensionX))); + Ok(bits) } } diff --git a/src/common/grid_sampler.rs b/src/common/grid_sampler.rs index 7fc5a4e..d368f47 100644 --- a/src/common/grid_sampler.rs +++ b/src/common/grid_sampler.rs @@ -95,7 +95,16 @@ pub trait GridSampler { dimensionY: u32, dst: Quadrilateral, src: Quadrilateral, - ) -> Result; + ) -> Result { + let transform = PerspectiveTransform::quadrilateralToQuadrilateral(dst, src)?; + + self.sample_grid( + image, + dimensionX, + dimensionY, + &[SamplerControl::new(dimensionX, dimensionY, transform)], + ) + } fn sample_grid( &self, @@ -103,7 +112,68 @@ pub trait GridSampler { dimensionX: u32, dimensionY: u32, controls: &[SamplerControl], - ) -> Result; + ) -> Result { + if dimensionX == 0 || dimensionY == 0 { + return Err(Exceptions::NOT_FOUND); + } + let mut bits = BitMatrix::new(dimensionX, dimensionY)?; + let mut points = vec![Point::default(); dimensionX as usize]; + for y in 0..dimensionY { + // for (int y = 0; y < dimensionY; y++) { + let max = points.len(); + let i_value = y as f32 + 0.5; + let mut x = 0; + while x < max { + // for (int x = 0; x < max; x += 2) { + points[x].x = (x as f32) + 0.5; + points[x].y = i_value; + x += 1; + } + + controls + .first() + .unwrap() + .transform + .transform_points_single(&mut points); + // Quick check to see if points transformed to something inside the image; + // sufficient to check the endpoints + self.checkAndNudgePoints(image, &mut points)?; + // try { + let mut x = 0; + while x < max { + // for (int x = 0; x < max; x += 2) { + // if points[x] as u32 >= image.getWidth() || points[x + 1] as u32 >= image.getHeight() + // { + // return Err(Exceptions::notFound( + // "index out of bounds, see documentation in file for explanation".to_owned(), + // )); + // } + if image + .try_get(points[x].x as u32, points[x].y as u32) + .ok_or(Exceptions::not_found_with( + "index out of bounds, see documentation in file for explanation", + ))? + { + // Black(-ish) pixel + bits.set(x as u32, y); + } + x += 1; + } + // } catch (ArrayIndexOutOfBoundsException aioobe) { + // // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting + // // transform gets "twisted" such that it maps a straight line of points to a set of points + // // whose endpoints are in bounds, but others are not. There is probably some mathematical + // // way to detect this about the transformation that I don't know yet. + // // This results in an ugly runtime exception despite our clever checks above -- can't have + // // that. We could check each point's coordinates but that feels duplicative. We settle for + // // catching and wrapping ArrayIndexOutOfBoundsException. + // throw NotFoundException.getNotFoundInstance(); + // } + } + // dbg!(bits.to_string()); + + Ok(bits) + } /** *

Checks a set of points that have been transformed to sample points on an image against diff --git a/src/common/perspective_transform.rs b/src/common/perspective_transform.rs index 5e42be3..cad843d 100644 --- a/src/common/perspective_transform.rs +++ b/src/common/perspective_transform.rs @@ -81,15 +81,28 @@ impl PerspectiveTransform { Ok(s_to_q * q_to_s) } + pub fn transform_point(&self, point: Point) -> Point { + let x = point.x; + let y = point.y; + let denominator = self.a13 * x + self.a23 * y + self.a33; + Point::new( + (self.a11 * x + self.a21 * y + self.a31) / denominator, + (self.a12 * x + self.a22 * y + self.a32) / denominator, + ) + } + pub fn transform_points_single(&self, points: &mut [Point]) { for point in points.iter_mut() { - // for (int i = 0; i < maxI; i += 2) { - let x = point.x; - let y = point.y; - let denominator = self.a13 * x + self.a23 * y + self.a33; - point.x = (self.a11 * x + self.a21 * y + self.a31) / denominator; - point.y = (self.a12 * x + self.a22 * y + self.a32) / denominator; + *point = self.transform_point(Point::new(point.x, point.y)); } + // for point in points.iter_mut() { + // // for (int i = 0; i < maxI; i += 2) { + // let x = point.x; + // let y = point.y; + // let denominator = self.a13 * x + self.a23 * y + self.a33; + // point.x = (self.a11 * x + self.a21 * y + self.a31) / denominator; + // point.y = (self.a12 * x + self.a22 * y + self.a32) / denominator; + // } } pub fn transform_points_double(&self, x_values: &mut [f32], y_valuess: &mut [f32]) { diff --git a/src/rxing_result_point.rs b/src/rxing_result_point.rs index 1e8d28b..78695c7 100644 --- a/src/rxing_result_point.rs +++ b/src/rxing_result_point.rs @@ -260,6 +260,12 @@ impl From<(i32, i32)> for Point { } } +impl From<(u32, u32)> for Point { + fn from(value: (u32, u32)) -> Self { + Self::new(value.0 as f32, value.1 as f32) + } +} + #[cfg(test)] mod tests { use super::Point; From 111baaab81aa0a10fe68778fabb40bd025cb1a7d Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Wed, 8 Mar 2023 14:37:45 -0600 Subject: [PATCH 02/72] clippy --fix --- src/aztec/DetectorTest.rs | 6 +++--- src/common/default_grid_sampler.rs | 5 ++--- src/common/quad.rs | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/aztec/DetectorTest.rs b/src/aztec/DetectorTest.rs index c3074cc..5a21d0d 100644 --- a/src/aztec/DetectorTest.rs +++ b/src/aztec/DetectorTest.rs @@ -124,9 +124,9 @@ fn test_error_in_parameter_locator(data: &str) { let r = r.expect("result already tested as ok"); assert_eq!(r.getNbLayers(), layers); assert_eq!(r.isCompact(), compact); - let res = decoder::decode(&r).expect(&format!( - "decode should be ok: {isMirror}, {error1}, {error2}" - )); + let res = decoder::decode(&r).unwrap_or_else(|_| { + panic!("decode should be ok: {isMirror}, {error1}, {error2}") + }); assert_eq!(data, res.getText()); } } diff --git a/src/common/default_grid_sampler.rs b/src/common/default_grid_sampler.rs index 96ec72a..556642f 100644 --- a/src/common/default_grid_sampler.rs +++ b/src/common/default_grid_sampler.rs @@ -45,9 +45,8 @@ impl GridSampler for DefaultGridSampler { // To deal with remaining examples (see #251 and #267) of "numercial instabilities" that have not been // prevented with the Quadrilateral.h:IsConvex() check, we check for all boundary points of the grid to // be inside. - let isInside = |p: Point| -> bool { - return image.is_in(transform.transform_point(p.centered())); - }; + let isInside = + |p: Point| -> bool { image.is_in(transform.transform_point(p.centered())) }; for y in (p0.y as i32)..(p1.y as i32) { // for (int y = y0; y < y1; ++y) if !isInside(point(p0.x, y as f32)) || !isInside(point(p1.x - 1.0, y as f32)) { diff --git a/src/common/quad.rs b/src/common/quad.rs index 9873d8b..688a1dd 100644 --- a/src/common/quad.rs +++ b/src/common/quad.rs @@ -171,7 +171,7 @@ impl Quadrilateral { let mirror = if let Some(m) = mirror { m } else { false }; - let mut res = self.clone(); + let mut res = *self; res.0.rotate_left(((n + 4) % 4) as usize); // std::rotate_copy(q.begin(), q.begin() + ((n + 4) % 4), q.end(), res.begin()); if mirror { From 5593a8f8f770c6e2f94b970e77ab1bf1ca430241 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Wed, 8 Mar 2023 18:51:48 -0600 Subject: [PATCH 03/72] move ported cpp_shared resources --- .../cpp_essentials}/bitmatrix_cursor.rs | 0 .../cpp_essentials}/direction.rs | 0 .../cpp_essentials}/dm_regression_line.rs | 0 .../cpp_essentials}/edge_tracer.rs | 4 ++-- src/common/cpp_essentials/mod.rs | 22 ++++++++++++++++++ .../cpp_essentials}/regression_line.rs | 0 .../cpp_essentials}/step_result.rs | 0 .../cpp_essentials}/util.rs | 0 .../cpp_essentials}/value.rs | 0 src/common/mod.rs | 2 ++ .../detector/zxing_cpp_detector/mod.rs | 23 +++++++------------ src/qrcode/cpp_port/decoder/mod.rs | 0 src/qrcode/cpp_port/mod.rs | 2 ++ src/qrcode/mod.rs | 2 ++ 14 files changed, 38 insertions(+), 17 deletions(-) rename src/{datamatrix/detector/zxing_cpp_detector => common/cpp_essentials}/bitmatrix_cursor.rs (100%) rename src/{datamatrix/detector/zxing_cpp_detector => common/cpp_essentials}/direction.rs (100%) rename src/{datamatrix/detector/zxing_cpp_detector => common/cpp_essentials}/dm_regression_line.rs (100%) rename src/{datamatrix/detector/zxing_cpp_detector => common/cpp_essentials}/edge_tracer.rs (99%) create mode 100644 src/common/cpp_essentials/mod.rs rename src/{datamatrix/detector/zxing_cpp_detector => common/cpp_essentials}/regression_line.rs (100%) rename src/{datamatrix/detector/zxing_cpp_detector => common/cpp_essentials}/step_result.rs (100%) rename src/{datamatrix/detector/zxing_cpp_detector => common/cpp_essentials}/util.rs (100%) rename src/{datamatrix/detector/zxing_cpp_detector => common/cpp_essentials}/value.rs (100%) create mode 100644 src/qrcode/cpp_port/decoder/mod.rs create mode 100644 src/qrcode/cpp_port/mod.rs diff --git a/src/datamatrix/detector/zxing_cpp_detector/bitmatrix_cursor.rs b/src/common/cpp_essentials/bitmatrix_cursor.rs similarity index 100% rename from src/datamatrix/detector/zxing_cpp_detector/bitmatrix_cursor.rs rename to src/common/cpp_essentials/bitmatrix_cursor.rs diff --git a/src/datamatrix/detector/zxing_cpp_detector/direction.rs b/src/common/cpp_essentials/direction.rs similarity index 100% rename from src/datamatrix/detector/zxing_cpp_detector/direction.rs rename to src/common/cpp_essentials/direction.rs diff --git a/src/datamatrix/detector/zxing_cpp_detector/dm_regression_line.rs b/src/common/cpp_essentials/dm_regression_line.rs similarity index 100% rename from src/datamatrix/detector/zxing_cpp_detector/dm_regression_line.rs rename to src/common/cpp_essentials/dm_regression_line.rs diff --git a/src/datamatrix/detector/zxing_cpp_detector/edge_tracer.rs b/src/common/cpp_essentials/edge_tracer.rs similarity index 99% rename from src/datamatrix/detector/zxing_cpp_detector/edge_tracer.rs rename to src/common/cpp_essentials/edge_tracer.rs index 186aa81..3cb99d8 100644 --- a/src/datamatrix/detector/zxing_cpp_detector/edge_tracer.rs +++ b/src/common/cpp_essentials/edge_tracer.rs @@ -10,9 +10,9 @@ use super::{BitMatrixCursor, Direction, RegressionLine, StepResult, Value}; #[derive(Clone)] pub struct EdgeTracer<'a> { - pub(super) img: &'a BitMatrix, + pub(crate) img: &'a BitMatrix, - pub(super) p: Point, // current position + pub(crate) p: Point, // current position d: Point, // current direction // pub history: Option<&'a mut ByteMatrix>, // = nullptr; diff --git a/src/common/cpp_essentials/mod.rs b/src/common/cpp_essentials/mod.rs new file mode 100644 index 0000000..50ff468 --- /dev/null +++ b/src/common/cpp_essentials/mod.rs @@ -0,0 +1,22 @@ +pub mod bitmatrix_cursor; +pub mod direction; +pub mod dm_regression_line; +pub mod edge_tracer; +pub mod regression_line; +pub mod step_result; +pub mod util; +pub mod value; +pub mod concentric_finder; +pub mod pattern; + + +pub use bitmatrix_cursor::*; +pub use direction::*; +pub use dm_regression_line::*; +pub use edge_tracer::*; +pub use regression_line::*; +pub use step_result::*; +pub use util::*; +pub use value::*; +pub use concentric_finder::*; +pub use pattern::*; \ No newline at end of file diff --git a/src/datamatrix/detector/zxing_cpp_detector/regression_line.rs b/src/common/cpp_essentials/regression_line.rs similarity index 100% rename from src/datamatrix/detector/zxing_cpp_detector/regression_line.rs rename to src/common/cpp_essentials/regression_line.rs diff --git a/src/datamatrix/detector/zxing_cpp_detector/step_result.rs b/src/common/cpp_essentials/step_result.rs similarity index 100% rename from src/datamatrix/detector/zxing_cpp_detector/step_result.rs rename to src/common/cpp_essentials/step_result.rs diff --git a/src/datamatrix/detector/zxing_cpp_detector/util.rs b/src/common/cpp_essentials/util.rs similarity index 100% rename from src/datamatrix/detector/zxing_cpp_detector/util.rs rename to src/common/cpp_essentials/util.rs diff --git a/src/datamatrix/detector/zxing_cpp_detector/value.rs b/src/common/cpp_essentials/value.rs similarity index 100% rename from src/datamatrix/detector/zxing_cpp_detector/value.rs rename to src/common/cpp_essentials/value.rs diff --git a/src/common/mod.rs b/src/common/mod.rs index 8327876..d011f1d 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -112,6 +112,8 @@ pub use eci::*; mod quad; pub use quad::*; +pub mod cpp_essentials; + #[cfg(feature = "otsu_level")] mod otsu_level_binarizer; #[cfg(feature = "otsu_level")] diff --git a/src/datamatrix/detector/zxing_cpp_detector/mod.rs b/src/datamatrix/detector/zxing_cpp_detector/mod.rs index 1842d6d..c944119 100644 --- a/src/datamatrix/detector/zxing_cpp_detector/mod.rs +++ b/src/datamatrix/detector/zxing_cpp_detector/mod.rs @@ -1,18 +1,11 @@ -mod bitmatrix_cursor; mod cpp_new_detector; -mod direction; -mod dm_regression_line; -mod edge_tracer; -mod regression_line; -mod step_result; -pub(self) mod util; -mod value; -pub(self) use bitmatrix_cursor::*; +pub(self) use crate::common::cpp_essentials::bitmatrix_cursor::*; pub use cpp_new_detector::detect; -pub(self) use direction::*; -pub(self) use dm_regression_line::*; -pub(self) use edge_tracer::*; -pub(self) use regression_line::*; -pub(self) use step_result::*; -pub(self) use value::*; +pub(self) use crate::common::cpp_essentials::direction::*; +pub(self) use crate::common::cpp_essentials::dm_regression_line::*; +pub(self) use crate::common::cpp_essentials::edge_tracer::*; +pub(self) use crate::common::cpp_essentials::regression_line::*; +pub(self) use crate::common::cpp_essentials::step_result::*; +pub(self) use crate::common::cpp_essentials::value::*; +pub(self) use crate::common::cpp_essentials::util; diff --git a/src/qrcode/cpp_port/decoder/mod.rs b/src/qrcode/cpp_port/decoder/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/qrcode/cpp_port/mod.rs b/src/qrcode/cpp_port/mod.rs new file mode 100644 index 0000000..af7a0fb --- /dev/null +++ b/src/qrcode/cpp_port/mod.rs @@ -0,0 +1,2 @@ +pub mod decoder; +// pub mod detector; \ No newline at end of file diff --git a/src/qrcode/mod.rs b/src/qrcode/mod.rs index be88824..77ee545 100644 --- a/src/qrcode/mod.rs +++ b/src/qrcode/mod.rs @@ -8,6 +8,8 @@ pub use qr_code_reader::*; mod qr_code_writer; pub use qr_code_writer::*; +mod cpp_port; + #[cfg(test)] #[cfg(feature = "image")] mod QRCodeWriterTestCase; From bd2189cf08d94244c989c10cb3675c6b92ae510e Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 10 Mar 2023 13:36:05 -0600 Subject: [PATCH 04/72] ported pattern.h, no tests pass --- .../cpp_essentials/concentric_finder.rs | 1 + src/common/cpp_essentials/mod.rs | 9 +- src/common/cpp_essentials/pattern.rs | 585 ++++++++++++++++++ .../detector/zxing_cpp_detector/mod.rs | 4 +- src/qrcode/cpp_port/decoder/mod.rs | 1 + src/qrcode/cpp_port/mod.rs | 2 +- 6 files changed, 594 insertions(+), 8 deletions(-) create mode 100644 src/common/cpp_essentials/concentric_finder.rs create mode 100644 src/common/cpp_essentials/pattern.rs diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -0,0 +1 @@ + diff --git a/src/common/cpp_essentials/mod.rs b/src/common/cpp_essentials/mod.rs index 50ff468..e306a78 100644 --- a/src/common/cpp_essentials/mod.rs +++ b/src/common/cpp_essentials/mod.rs @@ -1,22 +1,21 @@ pub mod bitmatrix_cursor; +pub mod concentric_finder; pub mod direction; pub mod dm_regression_line; pub mod edge_tracer; +pub mod pattern; pub mod regression_line; pub mod step_result; pub mod util; pub mod value; -pub mod concentric_finder; -pub mod pattern; - pub use bitmatrix_cursor::*; +pub use concentric_finder::*; pub use direction::*; pub use dm_regression_line::*; pub use edge_tracer::*; +pub use pattern::*; pub use regression_line::*; pub use step_result::*; pub use util::*; pub use value::*; -pub use concentric_finder::*; -pub use pattern::*; \ No newline at end of file diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs new file mode 100644 index 0000000..7e559b4 --- /dev/null +++ b/src/common/cpp_essentials/pattern.rs @@ -0,0 +1,585 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +use crate::{common::Result, Exceptions}; + +pub type PatternType = u16; +pub type Pattern = [PatternType; N]; + +#[derive(Default)] +pub struct PatternRow(Vec); + +// pub struct PatternRow + Into + Copy>(Vec); + +impl PatternRow { + pub fn into_pattern_view(&self) -> PatternView { + PatternView::new(self) + } +} + +impl<'a> Iterator for PatternView<'_> { + type Item = PatternType; + + fn next(&mut self) -> Option { + if self.current + 1 > self.count { + return None; + } + + self.current = self.current + 1; + + Some(*self.data.0.get(self.current + self.start)?) + } +} + +pub struct PatternView<'a> { + data: &'a PatternRow, + start: usize, + count: usize, + current: usize, +} + +impl<'a> PatternView<'_> { + // A PatternRow always starts with the width of whitespace in front of the first black bar. + // The first element of the PatternView is the first bar. + pub fn new(bars: &'a PatternRow) -> PatternView<'a> { + PatternView { + data: bars, + start: 0, + count: bars.0.len(), + current: 0, + } + } + + pub fn with_config( + bars: &'a PatternRow, + start: usize, + size: usize, + base: usize, + end: usize, + ) -> PatternView<'a> { + PatternView { + data: bars, + start, + count: size, + current: base, + } + } + + pub fn data(&self) -> &PatternRow { + &self.data + } + pub fn begin(&self) -> Option { + Some(*self.data.0.get(self.start)?) + } + pub fn end(&self) -> Option { + Some(self.data.0[self.start + self.count]) + } + + // int sum(int n = 0) const { return std::accumulate(_data, _data + (n == 0 ? _size : n), 0); } + pub fn sum(&self, n: Option) -> PatternType { + let n = n.unwrap_or(self.count); + + self.data + .0 + .iter() + .skip(self.start) + .take(n) + .copied() + .sum::() + } + + pub fn size(&self) -> usize { + self.count + } + + // index is the number of bars and spaces from the first bar to the current position + pub fn index(&self) -> usize { + self.current - self.start - 1 /*return narrow_cast(_data - _base) - 1;*/ + } + pub fn pixelsInFront(&self) -> PatternType { + self.data + .0 + .iter() + .skip(self.start) + .take(self.current) + .copied() + .sum::() /*return std::accumulate(_base, _data, 0);*/ + } + pub fn pixelsTillEnd(&self) -> PatternType { + self.data + .0 + .iter() + .skip(self.start + self.current) + .copied() + .sum::() /*return std::accumulate(_base, _data + _size, 0) - 1;*/ + } + pub fn isAtFirstBar(&self) -> bool { + self.start == (self.current + 1) /*return _data == _base + 1;*/ + } + pub fn isAtLastBar(&self) -> bool { + self.current == self.start + self.count - 1 /*return _data + _size == _end - 1;*/ + } + pub fn isValidWithN(&self, n: usize) -> bool { + !self.data.0.is_empty() + && self.start <= self.current + && self.start + n <= (self.start + self.count) + /*return _data && _data >= _base && _data + n <= _end;*/ + } + pub fn isValid(&self) -> bool { + self.isValidWithN(self.size()) + } + + fn has_quiet_zone_before(&self, scale: f32, acceptIfAtFirstBar: Option) -> bool { + (acceptIfAtFirstBar.unwrap_or(false) && self.isAtLastBar()) + || Into::::into(self.data.0[self.count]) + >= Into::::into(self.sum(None)) * scale + } + // template + // bool hasQuietZoneBefore(float scale) const + // { + // return (acceptIfAtFirstBar && isAtFirstBar()) || _data[-1] >= sum() * scale; + // } + + pub fn hasQuietZoneAfter(&self, scale: f32, acceptIfAtLastBar: Option) -> bool { + (acceptIfAtLastBar.unwrap_or(true) && self.isAtLastBar()) + || Into::::into(self.data.0[self.count]) + >= Into::::into(self.sum(None)) * scale + } + + // template + // bool hasQuietZoneAfter(float scale) const + // { + // return (acceptIfAtLastBar && isAtLastBar()) || _data[_size] >= sum() * scale; + // } + + pub fn subView(&'a self, offset: usize, size: Option) -> PatternView<'a> { + let mut size = size.unwrap_or(0); + if (size == 0) { + size = self.count - offset; + } else if (size < 0) { + size = self.count - offset + size; + } + + PatternView { + data: self.data, + start: self.start + offset, + count: size, + current: self.current, + } + } + + // PatternView subView(int offset, int size = 0) const + // { + // // if(std::abs(size) > count()) + // // printf("%d > %d\n", std::abs(size), _count); + // // assert(std::abs(size) <= count()); + // if (size == 0) + // size = _size - offset; + // else if (size < 0) + // size = _size - offset + size; + // return {begin() + offset, std::max(size, 0), _base, _end}; + // } + + pub fn shift(&mut self, n: usize) -> bool { + self.start += n; + !self.data.0.is_empty() && self.start + self.count <= (self.start + self.count) + } + + // bool shift(int n) + // { + // return _data && ((_data += n) + _size <= _end); + // } + + pub fn skipPair(&mut self) -> bool { + self.shift(2) + } + + pub fn skipSymbol(&mut self) -> bool { + self.shift(self.count) + } + + pub fn skipSingle(&mut self /* maxWidth: usize */) -> bool { + self.shift(1) //&& _data[-1] <= maxWidth; + } + + pub fn extend(&mut self) { + self.count = std::cmp::max(0, self.count - self.start) + } +} + +impl<'a> std::ops::Index for PatternView<'_> { + type Output = PatternType; + + fn index(&self, index: isize) -> &Self::Output { + if index > self.data.0.len() as isize { + panic!("array index out of bounds") + } + if index >= 0 { + return &self[index as usize]; + } + if index.abs() > self.start as isize { + panic!("array index out of bounds") + } + let fetch_spot = (self.start as isize + index) as usize; + &self.data.0[fetch_spot] + } +} + +impl<'a> std::ops::Index for PatternView<'_> { + type Output = PatternType; + + fn index(&self, index: usize) -> &Self::Output { + if index > self.data.0.len() { + panic!("array index out of bounds") + } + &self.data.0.get(self.start + self.current).unwrap() + } +} + +/** + * @brief The BarAndSpace struct is a simple 2 element data structure to hold information about bar(s) and space(s). + * + * The operator[](int) can be used in combination with a PatternView + */ +struct BarAndSpace { + bar: T, + space: T, +} +impl BarAndSpace { + pub fn isValid(&self) -> bool { + self.bar != T::default() && self.space != T::default() + } +} + +impl std::ops::Index for BarAndSpace { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + match index { + 0 => &self.bar, + 1 => &self.space, + _ => panic!("Index out of range for BarAndSpace"), + } + } +} + +impl std::ops::IndexMut for BarAndSpace { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + match index { + 0 => &mut self.bar, + 1 => &mut self.space, + _ => panic!("Index out of range for BarAndSpace"), + } + } +} + +// using value_type = T; +// T bar = {}, space = {}; +// // even index -> bar, odd index -> space +// T& operator[](int i) { return reinterpret_cast(this)[i & 1]; } +// T operator[](int i) const { return reinterpret_cast(this)[i & 1]; } +// bool isValid() const { return bar != T{} && space != T{}; } +// }; + +type BarAndSpaceI = BarAndSpace; + +/** + * @brief FixedPattern describes a compile-time constant (start/stop) pattern. + * + * @param N number of bars/spaces + * @param SUM sum over all N elements (size of pattern in modules) + * @param IS_SPARCE whether or not the pattern contains '0's denoting 'wide' bars/spaces + */ +pub struct FixedPattern { + data: [u16; N], +} + +impl FixedPattern { + fn new(data: [u16; N]) -> Self { + FixedPattern { data } + } + + fn as_slice(&self) -> &[u16] { + &self.data + } + + fn size(&self) -> usize { + N + } +} + +impl std::ops::Index + for FixedPattern +{ + type Output = u16; + + fn index(&self, index: usize) -> &Self::Output { + &self.data[index] + } +} + +pub type FixedSparcePattern = FixedPattern; + +// template +// struct FixedPattern +// { +// using value_type = PatternRow::value_type; +// value_type _data[N]; +// constexpr value_type operator[](int i) const noexcept { return _data[i]; } +// constexpr const value_type* data() const noexcept { return _data; } +// constexpr int size() const noexcept { return N; } +// }; + +// template +// using FixedSparcePattern = FixedPattern; + +pub fn IsPattern( + view: &PatternView, + pattern: &FixedPattern, + space_in_pixel: Option, + min_quiet_zone: f32, + module_size_ref: f32, + relaxed_threshold: Option, +) -> f32 { + let relaxed_threshold = relaxed_threshold.unwrap_or(false); + + let width = view.sum(Some(LEN)); + if SUM > LEN && Into::::into(width) < SUM { + return 0.0; + } + + let module_size: f32 = (Into::::into(width)) / (SUM as f32); + + if min_quiet_zone != 0.0 + && (space_in_pixel.unwrap_or(f32::MAX)) < min_quiet_zone as f32 * module_size as f32 - 1.0 + { + return 0.0; + } + + let threshold = module_size_ref * (0.5 + (relaxed_threshold as u8) as f32 * 0.25) + 0.5; + + // the offset of 0.5 is to make the code less sensitive to quantization errors for small (near 1) module sizes. + // TODO: review once we have upsampling in the binarizer in place. + + for x in 0..LEN { + if (Into::::into(view[x]) - Into::::into(pattern[x]) * module_size_ref).abs() + > threshold + { + return 0.0; + } + } + + module_size +} + +pub fn IsRightGuard( + view: &PatternView, + pattern: &FixedPattern, + minQuietZone: f32, + moduleSizeRef: f32, +) -> bool { + let spaceInPixel = if view.isAtLastBar() { + None + } else { + Some(view.end().unwrap().into()) + }; + + IsPattern( + view, + pattern, + spaceInPixel, + minQuietZone, + moduleSizeRef, + None, + ) != 0.0 +} + +pub fn FindLeftGuardBy<'a, const LEN: usize, Pred: Fn(&PatternView, Option) -> bool>( + view: &'a PatternView, + minSize: usize, + isGuard: Pred, +) -> Result> { + const PREV_IDX: isize = -1; + + if (view.size() < minSize) { + return Err(Exceptions::ILLEGAL_STATE); + } + + let mut window = view.subView(0, Some(LEN)); + if (window.isAtFirstBar() && isGuard(&window, None)) { + return Ok(window); + } + let end = Into::::into(view.end().unwrap()) - minSize; + while window.start < end { + if (isGuard(&window, Some(Into::::into(window[PREV_IDX])))) { + return Ok(window); + } + + window.skipPair(); + } + // for (auto end = view.end() - minSize; window.data() < end; window.skipPair()) + // { + + // } + + Err(Exceptions::ILLEGAL_STATE) +} + +pub fn FindLeftGuard<'a, const LEN: usize, const SUM: usize, const IS_SPARCE: bool>( + view: &'a PatternView, + minSize: usize, + pattern: &FixedPattern, + minQuietZone: f32, +) -> Result> { + FindLeftGuardBy::(view, std::cmp::max(minSize, LEN), |window, spaceInPixel| { + IsPattern(&window, pattern, spaceInPixel, minQuietZone, 0.0, None) != 0.0 + }) +} + +pub fn NormalizedE2EPattern<'a, const LEN: usize, const LEN_MINUS_2: usize, const SUM: usize>( + view: &'a PatternView, +) -> [PatternType; LEN_MINUS_2] { + let moduleSize: f32 = Into::::into(view.sum(Some(LEN))) / SUM as f32; + + let mut e2e = [PatternType::default(); LEN_MINUS_2]; + + for i in 0..LEN_MINUS_2 { + let v: f32 = (Into::::into(view[i]) + Into::::into(view[i + 1])) / moduleSize; + e2e[i] = (v + 0.5) as PatternType; + } + + return e2e; +} + +pub fn NormalizedPattern<'a, const LEN: usize, const SUM: usize>( + view: &'a PatternView, +) -> Result<[PatternType; LEN]> { + let moduleSize: f32 = (Into::::into(view.sum(Some(LEN))) / SUM) as f32; + let mut err = SUM as isize; + let mut is = [PatternType::default(); LEN]; + let mut rs = [0.0; LEN]; + for i in 0..LEN { + // for (int i = 0; i < LEN; i++) { + let v: f32 = Into::::into(view[i]) / moduleSize; + is[i] = (v + 0.5) as PatternType; + rs[i] = v - Into::::into(is[i]); + err -= Into::::into(is[i]) as isize; + } + + if (err.abs() > 1) { + return Err(Exceptions::NOT_FOUND); + } + + if (err != 0) { + // let mi =if err > 0 { std::max_element(std::begin(rs), std::end(rs)) - std::begin(rs)} + // else {std::min_element(std::begin(rs), std::end(rs)) - std::begin(rs)}; + let mi = if err > 0 { + rs.iter() + .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) + } else { + rs.iter() + .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) + }; + let mi = mi.ok_or(Exceptions::ILLEGAL_STATE)?; + is[*mi as usize] += err as u16; + rs[*mi as usize] -= err as f32; + } + + Ok(is) +} + +fn GetPatternRow + Copy>(b_row: &[T], p_row: &mut PatternRow) { + p_row.0.clear(); + p_row.0.push(0); // first value is number of white pixels, here 0 + + let mut bit_pos = 0u16; + let mut count = 0; + + for next_bit in b_row.iter().copied() { + count += 1; + + if next_bit.into() != bit_pos { + p_row.0.push(count); + count = 0; + } + + bit_pos = next_bit.into(); + } + + count += 1; + p_row.0.push(count); // final value is number of white pixels after last black pixel + + if bit_pos != 0 { + p_row.0.push(0); // add final black pixel + } +} + +#[cfg(test)] +mod tests { + use super::{GetPatternRow, PatternRow}; + const N: usize = 33; + + #[test] + fn all_white() { + for s in 1..=N { + // for (int s = 1; s <= N; ++s) { + let t_in = vec![0_u16; s]; + // std::vector in(s, 0); + let mut pr = PatternRow::default(); + GetPatternRow(&t_in, &mut pr); + + assert_eq!(pr.0.len(), 1); + assert_eq!(pr.0[0], s as u16); + } + } + + #[test] + fn all_black() { + for s in 1..=N { + // for (int s = 1; s <= N; ++s) { + let t_in: Vec = vec![0xff; s]; + let mut pr = PatternRow::default(); + GetPatternRow(&t_in, &mut pr); + + assert_eq!(pr.0.len(), 3); + assert_eq!(pr.0[0], 0); + assert_eq!(pr.0[1], s as u16); + assert_eq!(pr.0[2], 0); + } + } + + #[test] + fn black_white() { + for s in 1..=N { + // for (int s = 1; s <= N; ++s) { + let mut t_in = vec![0_u16; N]; + t_in[..s].copy_from_slice(&vec![1; s]); + // std::fill_n(in.data(), s, 0xff); + let mut pr = PatternRow::default(); + GetPatternRow(&t_in, &mut pr); + + assert_eq!(pr.0.len(), 3); + assert_eq!(pr.0[0], 0); + assert_eq!(pr.0[1], s as u16); + assert_eq!(pr.0[2], (N - s) as u16); + } + } + + #[test] + fn white_black() { + for s in 0..N { + // for (int s = 0; s < N; ++s) { + let mut t_in: Vec = vec![0xff; N]; + t_in[..s].copy_from_slice(&vec![0; s]); + let mut pr = PatternRow::default(); + GetPatternRow(&t_in, &mut pr); + + assert_eq!(pr.0.len(), 3); + assert_eq!(pr.0[0], s as u16); + assert_eq!(pr.0[1], (N - s) as u16); + assert_eq!(pr.0[2], 0); + } + } +} diff --git a/src/datamatrix/detector/zxing_cpp_detector/mod.rs b/src/datamatrix/detector/zxing_cpp_detector/mod.rs index c944119..f28f043 100644 --- a/src/datamatrix/detector/zxing_cpp_detector/mod.rs +++ b/src/datamatrix/detector/zxing_cpp_detector/mod.rs @@ -1,11 +1,11 @@ mod cpp_new_detector; pub(self) use crate::common::cpp_essentials::bitmatrix_cursor::*; -pub use cpp_new_detector::detect; pub(self) use crate::common::cpp_essentials::direction::*; pub(self) use crate::common::cpp_essentials::dm_regression_line::*; pub(self) use crate::common::cpp_essentials::edge_tracer::*; pub(self) use crate::common::cpp_essentials::regression_line::*; pub(self) use crate::common::cpp_essentials::step_result::*; -pub(self) use crate::common::cpp_essentials::value::*; pub(self) use crate::common::cpp_essentials::util; +pub(self) use crate::common::cpp_essentials::value::*; +pub use cpp_new_detector::detect; diff --git a/src/qrcode/cpp_port/decoder/mod.rs b/src/qrcode/cpp_port/decoder/mod.rs index e69de29..8b13789 100644 --- a/src/qrcode/cpp_port/decoder/mod.rs +++ b/src/qrcode/cpp_port/decoder/mod.rs @@ -0,0 +1 @@ + diff --git a/src/qrcode/cpp_port/mod.rs b/src/qrcode/cpp_port/mod.rs index af7a0fb..9ac85ff 100644 --- a/src/qrcode/cpp_port/mod.rs +++ b/src/qrcode/cpp_port/mod.rs @@ -1,2 +1,2 @@ pub mod decoder; -// pub mod detector; \ No newline at end of file +// pub mod detector; From 520764e3207272222f01299b479ee0ae7bd9f376 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 10 Mar 2023 16:26:49 -0600 Subject: [PATCH 05/72] ported tests pass --- src/common/cpp_essentials/pattern.rs | 80 ++++++++++++++++++---------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index 7e559b4..b94ef0f 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -27,7 +27,7 @@ impl<'a> Iterator for PatternView<'_> { return None; } - self.current = self.current + 1; + self.current += 1; Some(*self.data.0.get(self.current + self.start)?) } @@ -57,7 +57,7 @@ impl<'a> PatternView<'_> { start: usize, size: usize, base: usize, - end: usize, + _end: usize, ) -> PatternView<'a> { PatternView { data: bars, @@ -68,7 +68,7 @@ impl<'a> PatternView<'_> { } pub fn data(&self) -> &PatternRow { - &self.data + self.data } pub fn begin(&self) -> Option { Some(*self.data.0.get(self.start)?) @@ -156,10 +156,10 @@ impl<'a> PatternView<'_> { pub fn subView(&'a self, offset: usize, size: Option) -> PatternView<'a> { let mut size = size.unwrap_or(0); - if (size == 0) { + if size == 0 { size = self.count - offset; - } else if (size < 0) { - size = self.count - offset + size; + } else if size < 0 { + size += self.count - offset; } PatternView { @@ -234,7 +234,7 @@ impl<'a> std::ops::Index for PatternView<'_> { if index > self.data.0.len() { panic!("array index out of bounds") } - &self.data.0.get(self.start + self.current).unwrap() + self.data.0.get(self.start + self.current).unwrap() } } @@ -353,7 +353,7 @@ pub fn IsPattern( let module_size: f32 = (Into::::into(width)) / (SUM as f32); if min_quiet_zone != 0.0 - && (space_in_pixel.unwrap_or(f32::MAX)) < min_quiet_zone as f32 * module_size as f32 - 1.0 + && (space_in_pixel.unwrap_or(f32::MAX)) < min_quiet_zone * module_size - 1.0 { return 0.0; } @@ -403,17 +403,17 @@ pub fn FindLeftGuardBy<'a, const LEN: usize, Pred: Fn(&PatternView, Option) ) -> Result> { const PREV_IDX: isize = -1; - if (view.size() < minSize) { + if view.size() < minSize { return Err(Exceptions::ILLEGAL_STATE); } let mut window = view.subView(0, Some(LEN)); - if (window.isAtFirstBar() && isGuard(&window, None)) { + if window.isAtFirstBar() && isGuard(&window, None) { return Ok(window); } let end = Into::::into(view.end().unwrap()) - minSize; while window.start < end { - if (isGuard(&window, Some(Into::::into(window[PREV_IDX])))) { + if isGuard(&window, Some(Into::::into(window[PREV_IDX]))) { return Ok(window); } @@ -434,7 +434,7 @@ pub fn FindLeftGuard<'a, const LEN: usize, const SUM: usize, const IS_SPARCE: bo minQuietZone: f32, ) -> Result> { FindLeftGuardBy::(view, std::cmp::max(minSize, LEN), |window, spaceInPixel| { - IsPattern(&window, pattern, spaceInPixel, minQuietZone, 0.0, None) != 0.0 + IsPattern(window, pattern, spaceInPixel, minQuietZone, 0.0, None) != 0.0 }) } @@ -450,7 +450,7 @@ pub fn NormalizedE2EPattern<'a, const LEN: usize, const LEN_MINUS_2: usize, cons e2e[i] = (v + 0.5) as PatternType; } - return e2e; + e2e } pub fn NormalizedPattern<'a, const LEN: usize, const SUM: usize>( @@ -468,11 +468,11 @@ pub fn NormalizedPattern<'a, const LEN: usize, const SUM: usize>( err -= Into::::into(is[i]) as isize; } - if (err.abs() > 1) { + if err.abs() > 1 { return Err(Exceptions::NOT_FOUND); } - if (err != 0) { + if err != 0 { // let mi =if err > 0 { std::max_element(std::begin(rs), std::end(rs)) - std::begin(rs)} // else {std::min_element(std::begin(rs), std::end(rs)) - std::begin(rs)}; let mi = if err > 0 { @@ -490,30 +490,54 @@ pub fn NormalizedPattern<'a, const LEN: usize, const SUM: usize>( Ok(is) } -fn GetPatternRow + Copy>(b_row: &[T], p_row: &mut PatternRow) { - p_row.0.clear(); - p_row.0.push(0); // first value is number of white pixels, here 0 +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum Color { + White = 0, + Black = 1 +} - let mut bit_pos = 0u16; +impl> From for Color { + fn from(value: T) -> Self { + match value.into() { + 0 => Color::White, + _ => Color::Black, + _=>Color::Black + } + } +} + +fn GetPatternRow + Copy + Default + From>(b_row: &[T], p_row: &mut PatternRow) { + p_row.0.clear(); + + if Color::from(p_row.0.first().copied().unwrap_or_default()) == Color::Black { + // first + p_row.0.push(0); + } + + let mut current_color = Color::from(p_row.0.first().copied().unwrap_or_default()); //if p_row.0.first().copied().unwrap_or_default() == 1 {Color::Black} else { Color::White}; let mut count = 0; - for next_bit in b_row.iter().copied() { - count += 1; + for bit in b_row.iter() { + let this_color = Color::from(*bit); - if next_bit.into() != bit_pos { + if current_color != this_color { p_row.0.push(count); count = 0; + + current_color = this_color; } - bit_pos = next_bit.into(); + count +=1 ; } - count += 1; - p_row.0.push(count); // final value is number of white pixels after last black pixel - - if bit_pos != 0 { - p_row.0.push(0); // add final black pixel + if count != 0 { + p_row.0.push(count); } + + if current_color == Color::Black { + p_row.0.push(0); + } + } #[cfg(test)] From 80c5e57632ae4d3b30cf21ba49a9578574a407a3 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sat, 11 Mar 2023 19:06:55 -0600 Subject: [PATCH 06/72] does not build, mostly ported concentric_finder --- src/common/cpp_essentials/bitmatrix_cursor.rs | 3 + .../cpp_essentials/concentric_finder.rs | 531 ++++++++++++++++++ .../cpp_essentials/dm_regression_line.rs | 20 +- src/common/cpp_essentials/edge_tracer.rs | 18 +- .../fast_edge_to_edge_counter.rs | 40 ++ src/common/cpp_essentials/mod.rs | 2 + src/common/cpp_essentials/pattern.rs | 119 +++- src/common/cpp_essentials/regression_line.rs | 19 +- src/common/quad.rs | 36 ++ src/rxing_result_point.rs | 32 ++ 10 files changed, 785 insertions(+), 35 deletions(-) create mode 100644 src/common/cpp_essentials/fast_edge_to_edge_counter.rs diff --git a/src/common/cpp_essentials/bitmatrix_cursor.rs b/src/common/cpp_essentials/bitmatrix_cursor.rs index 420eb2f..5cf6950 100644 --- a/src/common/cpp_essentials/bitmatrix_cursor.rs +++ b/src/common/cpp_essentials/bitmatrix_cursor.rs @@ -78,6 +78,7 @@ pub trait BitMatrixCursor { // } fn movedBy(self, d: Point) -> Self; + fn turnedBack(&self) -> Self;// { return {*img, p, back()}; } // { // auto res = *this; // res.p += d; @@ -160,6 +161,8 @@ pub trait BitMatrixCursor { res } + fn p(&self) -> Point; + // template // ARRAY readPattern(int range = 0) // { diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index 8b13789..6fffe69 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -1 +1,532 @@ +use crate::{ + common::{ + cpp_essentials::{ + Direction, FixedPattern, IsPattern, PatternRow, PatternType, PatternView, + }, + BitMatrix, Quadrilateral, + }, + point, Point, +}; +use super::{ + BitMatrixCursor, DMRegressionLine, EdgeTracer, FastEdgeToEdgeCounter, Pattern, RegressionLine, +}; + +pub fn CenterFromEnd + std::iter::Sum + Copy>( + pattern: &[T; N], + end: f32, +) -> f32 { + if (N == 5) { + let a: f32 = pattern[4].into() + pattern[3].into() + pattern[2].into() / 2.0; + let b: f32 = + pattern[4].into() + (pattern[3].into() + pattern[2].into() + pattern[1].into()) / 2.0; + let c: f32 = (pattern[4].into() + + pattern[3].into() + + pattern[2].into() + + pattern[1].into() + + pattern[0].into()) + / 2.0; + end - (2.0 * a + b + c) / 4.0 + } else if (N == 3) { + let a: f32 = pattern[2].into() + pattern[1].into() / 2.0; + let b: f32 = (pattern[2].into() + pattern[1].into() + pattern[0].into()) / 2.0; + end - (2.0 * a + b) / 3.0 + } else { + // aztec + let a: f32 = + pattern.iter().skip(N / 2 + 1).copied().sum::().into() + pattern[N / 2].into() / 2.0; + // let a = std::accumulate(pattern.begin() + (N/2 + 1), pattern.end(), pattern[N/2] / 2.0); + end - a + } +} + +pub fn ReadSymmetricPattern( + cur: &mut Cursor, + range: i32, +) -> Option> { + assert!(N % 2 == 1); + + assert!(range > 0); + + let mut range = range; + + let mut res: Pattern = [0; N]; + let s_2 = res.len() as isize / 2; + let mut cuo = cur.turnedBack(); + + let mut next = |cur: &mut Cursor, i: isize| { + let v = cur.stepToEdge(Some(1), Some(range), None); + res[(s_2 + i) as usize] = (res[(s_2 + i) as usize] as i32 + v) as u16; + // res[(s_2 + i) as usize] += v; + if (range != 0) { + range -= v; + } + + v + }; + + for i in 0..=s_2 { + // for (int i = 0; i <= s_2; ++i) { + if (!next(cur, i) != 0 || !next(&mut cuo, -i) != 0) { + return None; + } + } + res[s_2 as usize] -= 1; // the starting pixel has been counted twice, fix this + + Some(res) +} + +// default for RELAXED_THRESHOLD should be false +pub fn CheckSymmetricPattern< + const RELAXED_THRESHOLD: bool, + const LEN: usize, + const SUM: usize, + T: BitMatrixCursor, +>( + cur: &mut T, + pattern: &Pattern, + range: i32, + updatePosition: bool, +) -> i32 { + let mut range = range; + + let curFwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(cur); + let curBwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(&cur.turnedBack()); + + let centerFwd = curFwd.stepToNextEdge(range); + if (!(centerFwd != 0)) { + return 0; + } + let centerBwd = curBwd.stepToNextEdge(range); + if (!(centerBwd != 0)) { + return 0; + } + + assert!(range > 0); + let mut res: PatternRow = PatternRow::new(vec![0; LEN]); + let s_2 = (res.len()) / 2; + res[s_2] = (centerFwd + centerBwd - 1) as u16; // -1 because the starting pixel is counted twice + range -= res[s_2] as i32; + + let mut next = |cur: &FastEdgeToEdgeCounter, i: isize| { + let v = cur.stepToNextEdge(range); + res[(s_2 as isize + i) as usize] = v as u16; + range -= v; + + v + }; + + for i in 1..=s_2 { + // for (int i = 1; i <= s_2; ++i) { + if (!(next(&curFwd, i as isize) != 0) || !(next(&curBwd, -(i as isize)) != 0)) { + return 0; + } + } + + if (!(IsPattern( + &PatternView::new(&res), + &FixedPattern::::with_reference(pattern), + None, + 0.0, + 0.0, + Some(RELAXED_THRESHOLD), + ) != 0.0)) + { + return 0; + } + + if (updatePosition) { + cur.step(Some((res[s_2] as i32 / 2 - (centerBwd as i32 - 1)) as f32)); + } + + res.into_iter().sum::() as i32 +} + +pub fn AverageEdgePixels( + cur: &mut T, + range: i32, + numOfEdges: u32, +) -> Option { + let mut sum = Point::default(); + + for i in 0..numOfEdges { + // for (int i = 0; i < numOfEdges; ++i) { + if (!cur.isInSelf()) { + return None; + } + cur.stepToEdge(Some(1), Some(range), None); + sum += cur.p().centered() + (cur.p() + cur.back()).centered() + // sum += centered(cur.p) + centered(cur.p + cur.back()); + // log(cur.p + cur.back(), 2); + } + Some(sum / (2 * numOfEdges) as f32) +} + +pub fn CenterOfDoubleCross( + image: &BitMatrix, + center: Point, + range: i32, + numOfEdges: u32, +) -> Option { + let mut sum = Point::default(); + + for d in [ + point(0.0, 1.0), + point(1.0, 0.0), + point(1.0, 1.0), + point(1.0, -1.0), + ] { + // for (auto d : {PointI{0, 1}, {1, 0}, {1, 1}, {1, -1}}) { + let avr1 = AverageEdgePixels(&mut EdgeTracer::new(image, center, d), range, numOfEdges)?; + let avr2 = AverageEdgePixels(&mut EdgeTracer::new(image, center, -d), range, numOfEdges)?; + + sum += avr1 + avr2; + } + Some(sum / 8.0) +} + +pub fn CenterOfRing( + image: &BitMatrix, + center: Point, + range: i32, + nth: i32, + requireCircle: bool, +) -> Option { + // range is the approximate width/height of the nth ring, if nth>1 then it would be plausible to limit the search radius + // to approximately range / 2 * sqrt(2) == range * 0.75 but it turned out to be too limiting with realworld/noisy data. + let radius = range; + let inner = nth < 0; + let nth = nth.abs(); + // log(center, 3); + let mut cur = EdgeTracer::new(image, center, point(0.0, 1.0)); + if (!(cur.stepToEdge(Some(nth), Some(radius), Some(inner)) != 0)) { + return None; + } + cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup + let edgeDir = if inner { + Direction::Left + } else { + Direction::Right + }; + + let mut neighbourMask = 0; + let start = cur.p(); + let mut sum = Point::default(); + let mut n = 0; + loop { + // log(cur.p, 4); + sum += cur.p().centered(); + n += 1; + + // find out if we come full circle around the center. 8 bits have to be set in the end. + neighbourMask |= (1 + << (4.0 + Point::dot(Point::bresenhamDirection(cur.p() - center), point(1.0, 3.0))) + as u32); + + if (!cur.stepAlongEdge(edgeDir, None)) { + return None; + } + + // use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate + if (Point::maxAbsComponent(cur.p - center) > radius as f32 + || center == cur.p + || n > 4 * 2 * range) + { + return None; + } + + if !(cur.p != start) { + break; + } + } //while (cur.p != start); + + if (requireCircle && neighbourMask != 0b111101111) { + return None; + } + + Some(sum / n as f32) +} + +pub fn CenterOfRings( + image: &BitMatrix, + center: Point, + range: i32, + numOfRings: u32, +) -> Option { + let mut n = numOfRings; + let mut sum = numOfRings * center; + for i in 1..numOfRings { + // for (int i = 1; i < numOfRings; ++i) { + let c = CenterOfRing(image, center, range, i as i32 + 1, false)?; + + // TODO: decide whether this wheighting depending on distance to the center is worth it + let weight = numOfRings - i; + sum += weight * c; + n += weight; + } + Some(sum / n as f32) +} + +pub fn FinetuneConcentricPatternCenter( + image: &BitMatrix, + center: Point, + range: i32, + finderPatternSize: u32, +) -> Option { + // make sure we have at least one path of white around the center + let res = CenterOfRing(image, center, range, 1, false)?; + + let center = res; + + let mut res = CenterOfRings(image, center, range, finderPatternSize / 2); + + if (res.is_none() || !image.get_point(res?)) { + res = CenterOfDoubleCross(image, (center), range, finderPatternSize / 2 + 1); + } + if (res.is_none() || !image.get_point(res?)) { + res = Some(center); + } + if (res.is_none() || !image.get_point(res?)) { + return None; + } + + res +} + +pub fn CollectRingPoints( + image: &BitMatrix, + center: Point, + range: i32, + edgeIndex: i32, + backup: bool, +) -> Vec { + let centerI = center.round(); + let radius = range; + let mut cur = EdgeTracer::new(image, centerI, point(0.0, 1.0)); + if (!(cur.stepToEdge(Some(edgeIndex), Some(radius), Some(backup)) != 0)) { + return Vec::default(); + } + cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup + let edgeDir = if backup { + Direction::Left + } else { + Direction::Right + }; + + let mut neighbourMask = 0; + let start = cur.p(); + let mut points = Vec::::with_capacity(4 * range as usize); + + loop { + // log(cur.p, 4); + points.push((cur.p().centered())); + + // find out if we come full circle around the center. 8 bits have to be set in the end. + neighbourMask |= (1 + << (4.0 + Point::dot(Point::bresenhamDirection(cur.p - centerI), point(1.0, 3.0))) + as u32); + + if (!cur.stepAlongEdge(edgeDir, None)) { + return Vec::default(); + } + + // use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate + if (Point::maxAbsComponent(cur.p - centerI) > radius as f32 + || centerI == cur.p + || (points).len() > 4 * 2 * range as usize) + { + return Vec::default(); + } + + if !(cur.p != start) { + break; + } + } //while (cur.p != start); + + if (neighbourMask != 0b111101111) { + return Vec::default(); + } + + points +} + +pub fn FitQadrilateralToPoints(center: Point, points: &mut [Point]) -> Option { + let dist2Center = |a, b| Point::distance(a, center) < Point::distance(b, center); + // rotate points such that the first one is the furthest away from the center (hence, a corner) + + let max_by_pred = |a: &Point, b: &Point| { + if dist2Center(*a, *b) { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Less + } + }; + + let max = points.iter().copied().max_by(max_by_pred)?; + + let pos = points.iter().position(|e| *e == max)?; + + points.rotate_left(pos); + // std::rotate(points.begin(), std::max_element(points.begin(), points.end(), dist2Center), points.end()); + + let mut corners = [Point::default(); 4]; + corners[0] = points[0]; + // find the oposite corner by looking for the farthest point near the oposite point + points[(points.len() * 3 / 8)..=(points.len() * 5 / 8)] + .iter() + .copied() + .max_by(max_by_pred)?; + // corners[2] = std::max_element(&points[Size(points) * 3 / 8], &points[Size(points) * 5 / 8], dist2Center); + // find the two in between corners by looking for the points farthest from the long diagonal + let l = DMRegressionLine::with_two_points(corners[0], corners[2]); + let dist2Diagonal = /*[l = RegressionLine(*corners[0], *corners[2])]*/| a, b| { l.distance_single(a) < l.distance_single(b) }; + + let diagonal_max_by_pred = |p1: &Point, p2: &Point| { + if dist2Diagonal(*p1, *p2) { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Less + } + }; + corners[1] = points[(points.len() * 1 / 8)..=(points.len() * 3 / 8)] + .iter() + .copied() + .max_by(diagonal_max_by_pred)?; + // corners[1] = std::max_element(&points[Size(points) * 1 / 8], &points[Size(points) * 3 / 8], dist2Diagonal); + corners[3] = points[(points.len() * 5 / 8)..=(points.len() * 7 / 8)] + .iter() + .copied() + .max_by(diagonal_max_by_pred)?; + // corners[3] = std::max_element(&points[Size(points) * 5 / 8], &points[Size(points) * 7 / 8], dist2Diagonal); + + let lines = [ + DMRegressionLine::with_two_points((corners[0] + 1.0), corners[1]), + DMRegressionLine::with_two_points((corners[1] + 1.0), corners[2]), + DMRegressionLine::with_two_points((corners[2] + 1.0), corners[3]), + DMRegressionLine::with_two_points((corners[3] + 1.0), (*points.last()? + 1.0)), + ]; + // std::array lines{RegressionLine{corners[0] + 1, corners[1]}, RegressionLine{corners[1] + 1, corners[2]}, + // RegressionLine{corners[2] + 1, corners[3]}, RegressionLine{corners[3] + 1, &points.back() + 1}}; + if lines.iter().any(|line| !line.isValid()) { + return None; + } + + let mut res = Quadrilateral::default(); + for i in 0..4 { + // for (int i = 0; i < 4; ++i) { + res[i] = DMRegressionLine::intersect(&lines[i], &lines[(i + 1) % 4])?; + } + + Some(res) +} + +pub fn QuadrilateralIsPlausibleSquare(q: &Quadrilateral, lineIndex: usize) -> bool { + let mut m = f64::default(); + // let mut M = f64::default(); + + m = Point::distance(q[0], q[3]) as f64; //M = distance(q[0], q[3]); + let mut M = m; + + for i in 1..4 { + // for (int i = 1; i < 4; ++i) + + UpdateMinMaxFloat(&mut m, &mut M, Point::distance(q[i - 1], q[i]) as f64); + } + + m >= (lineIndex * 2) as f64 && m > M / 3.0 +} + +pub fn FitSquareToPoints( + image: &BitMatrix, + center: Point, + range: i32, + lineIndex: i32, + backup: bool, +) -> Option { + let mut points = CollectRingPoints(image, center, range, lineIndex, backup); + if (points.is_empty()) { + return None; + } + + let res = FitQadrilateralToPoints(center, &mut points)?; + if (!QuadrilateralIsPlausibleSquare(&res, (lineIndex - i32::from(backup)) as usize)) { + return None; + } + + Some(res) +} + +pub fn FindConcentricPatternCorners( + image: &BitMatrix, + center: Point, + range: i32, + lineIndex: i32, +) -> Option { + let innerCorners = FitSquareToPoints(image, center, range, lineIndex, false)?; + + let outerCorners = FitSquareToPoints(image, center, range, lineIndex + 1, true)?; + + let res = Quadrilateral::blend(&innerCorners, &outerCorners); + + // for p in innerCorners{ + // log(p, 3);} + + // for p in outerCorners{ + // log(p, 3);} + + // for p in res{ + // log(p, 3);} + + Some(res) +} + +#[derive(Default)] +pub struct ConcentricPattern { + p: Point, + size: usize, +} + + pub fn LocateConcentricPattern( image:&BitMatrix, pattern:&Pattern, center:Point, range:i32) -> Option +{ + let mut cur = EdgeTracer::new(image, center, Point::default()); + let mut minSpread = image.getWidth() as i32; + let mut maxSpread = 0_i32; + for d in [point(0.0,1.0), point(1.0,0.0)] { + // for (auto d : {PointI{0, 1}, {1, 0}}) { + cur.setDirection(d); // THIS COULD POSSIBLY BE WRONG, WE MIGHT MEAN TO CLONE cur EACH RUN? + let spread = CheckSymmetricPattern(&mut cur, pattern, range, true); + if (!(spread != 0)) + {return None} + UpdateMinMax(&mut minSpread, &mut maxSpread, spread); + } + +//#if 1 +for d in [point(1.0,1.0), point(1.0,-1.0)] { + // for (auto d : {PointI{1, 1}, {1, -1}}) { + cur.setDirection(d);// THIS COULD POSSIBLY BE WRONG, WE MIGHT MEAN TO CLONE cur EACH RUN? + let spread = CheckSymmetricPattern(&mut cur, pattern, range * 2, false); + if (!(spread != 0)) + {return None} + UpdateMinMax(&mut minSpread, &mut maxSpread, spread); + } +//#endif + + if (maxSpread > 5 * minSpread) + {return None} + + let newCenter = FinetuneConcentricPatternCenter(image, cur.p(), range, pattern.len() as u32)?; + + Some(ConcentricPattern{*newCenter, (maxSpread + minSpread) / 2}) +} + +fn UpdateMinMax(min: &mut T, max: &mut T, val: T) { + *min = std::cmp::min(*min, val); + *max = std::cmp::max(*max, val); +} + +fn UpdateMinMaxFloat(min: &mut f64, max: &mut f64, val: f64) { + *min = f64::min(*min, val); + *max = f64::max(*max, val); +} diff --git a/src/common/cpp_essentials/dm_regression_line.rs b/src/common/cpp_essentials/dm_regression_line.rs index d6e8d9e..8a574bd 100644 --- a/src/common/cpp_essentials/dm_regression_line.rs +++ b/src/common/cpp_essentials/dm_regression_line.rs @@ -1,5 +1,5 @@ use crate::common::Result; -use crate::{Exceptions, Point}; +use crate::{Exceptions, Point, point}; use super::{ util::{float_max, float_min}, @@ -211,9 +211,27 @@ impl RegressionLine for DMRegressionLine { Point::dot(self.direction_inward, self.normal()) > 0.5 // angle between original and new direction is at most 60 degree } + + fn a(&self) -> f32 { + self.a + } + + fn b(&self) -> f32 { + self.b + } + + fn c(&self) -> f32 { + self.c + } } impl DMRegressionLine { + pub fn with_two_points(point1: Point, point2: Point) -> Self { + let mut new_rl = DMRegressionLine::default(); + new_rl.evaluate(&[point1, point2]); + new_rl + } + // template fn average(c: &[f64], f: T) -> f64 where diff --git a/src/common/cpp_essentials/edge_tracer.rs b/src/common/cpp_essentials/edge_tracer.rs index 3cb99d8..0f78223 100644 --- a/src/common/cpp_essentials/edge_tracer.rs +++ b/src/common/cpp_essentials/edge_tracer.rs @@ -126,6 +126,13 @@ impl BitMatrixCursor for EdgeTracer<'_> { res } + fn turnedBack(&self) -> Self { + let mut res = self.clone(); + res.d = res.back(); + + res + } + /** * @brief stepToEdge advances cursor to one step behind the next (or n-th) edge. * @param nth number of edges to pass @@ -134,9 +141,9 @@ impl BitMatrixCursor for EdgeTracer<'_> { * @return number of steps taken or 0 if moved outside of range/image */ fn stepToEdge(&mut self, nth: Option, range: Option, backup: Option) -> i32 { - let mut nth = if let Some(nth) = nth { nth } else { 1 }; - let range = if let Some(r) = range { r } else { 0 }; - let backup = if let Some(b) = backup { b } else { false }; + let mut nth = nth.unwrap_or(1); //if let Some(nth) = nth { nth } else { 1 }; + let range = range.unwrap_or(0);//if let Some(r) = range { r } else { 0 }; + let backup = backup.unwrap_or(false);//if let Some(b) = backup { b } else { false }; // TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt() let mut steps = 0; let mut lv = self.testAt(self.p); @@ -155,6 +162,11 @@ impl BitMatrixCursor for EdgeTracer<'_> { self.p += self.d * steps; steps * i32::from(nth == 0) } + + fn p(&self) -> Point { + self.p + } + } impl<'a> EdgeTracer<'_> { diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs new file mode 100644 index 0000000..835cb2a --- /dev/null +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -0,0 +1,40 @@ +use super::BitMatrixCursor; + +pub struct FastEdgeToEdgeCounter { + // const uint8_t* p = nullptr; + // int stride = 0; + // int stepsToBorder = 0; +} + +impl FastEdgeToEdgeCounter +{ + pub fn new(cur: &T) -> Self { + todo!() + // stride = cur.d.y * cur.img->width() + cur.d.x; + // p = cur.img->row(cur.p.y).begin() + cur.p.x; + + // int maxStepsX = cur.d.x ? (cur.d.x > 0 ? cur.img->width() - 1 - cur.p.x : cur.p.x) : INT_MAX; + // int maxStepsY = cur.d.y ? (cur.d.y > 0 ? cur.img->height() - 1 - cur.p.y : cur.p.y) : INT_MAX; + // stepsToBorder = std::min(maxStepsX, maxStepsY); + } + + pub fn stepToNextEdge(&self, range: i32) -> i32 + { + todo!() + // int maxSteps = std::min(stepsToBorder, range); + // int steps = 0; + // do { + // if (++steps > maxSteps) { + // if (maxSteps == stepsToBorder) + // break; + // else + // return 0; + // } + // } while (p[steps * stride] == p[0]); + + // p += steps * stride; + // stepsToBorder -= steps; + + // return steps; + } +} \ No newline at end of file diff --git a/src/common/cpp_essentials/mod.rs b/src/common/cpp_essentials/mod.rs index e306a78..3f040c0 100644 --- a/src/common/cpp_essentials/mod.rs +++ b/src/common/cpp_essentials/mod.rs @@ -8,6 +8,7 @@ pub mod regression_line; pub mod step_result; pub mod util; pub mod value; +pub mod fast_edge_to_edge_counter; pub use bitmatrix_cursor::*; pub use concentric_finder::*; @@ -19,3 +20,4 @@ pub use regression_line::*; pub use step_result::*; pub use util::*; pub use value::*; +pub use fast_edge_to_edge_counter::*; diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index b94ef0f..ff5cf36 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -8,17 +8,49 @@ use crate::{common::Result, Exceptions}; pub type PatternType = u16; pub type Pattern = [PatternType; N]; -#[derive(Default)] +#[derive(Default,Debug)] pub struct PatternRow(Vec); // pub struct PatternRow + Into + Copy>(Vec); impl PatternRow { +pub fn new( v: Vec) -> Self { + Self(v) +} + +pub fn len(&self) -> usize { + self.0.len() +} + pub fn into_pattern_view(&self) -> PatternView { PatternView::new(self) } } +impl IntoIterator for PatternRow { + type Item = PatternType; + + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl std::ops::Index for PatternRow { + type Output = PatternType; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + +impl std::ops::IndexMut for PatternRow { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} + impl<'a> Iterator for PatternView<'_> { type Item = PatternType; @@ -33,6 +65,7 @@ impl<'a> Iterator for PatternView<'_> { } } +#[derive(Debug, Clone, Copy)] pub struct PatternView<'a> { data: &'a PatternRow, start: usize, @@ -96,7 +129,7 @@ impl<'a> PatternView<'_> { // index is the number of bars and spaces from the first bar to the current position pub fn index(&self) -> usize { - self.current - self.start - 1 /*return narrow_cast(_data - _base) - 1;*/ + self.current - self.start /*return narrow_cast(_data - _base) - 1;*/ } pub fn pixelsInFront(&self) -> PatternType { self.data @@ -183,7 +216,7 @@ impl<'a> PatternView<'_> { // } pub fn shift(&mut self, n: usize) -> bool { - self.start += n; + self.current += n; !self.data.0.is_empty() && self.start + self.count <= (self.start + self.count) } @@ -234,7 +267,15 @@ impl<'a> std::ops::Index for PatternView<'_> { if index > self.data.0.len() { panic!("array index out of bounds") } - self.data.0.get(self.start + self.current).unwrap() + self.data.0.get(self.start + self.current + index).unwrap() + } +} + +impl<'a> std::ops::Index for PatternView<'_> { + type Output = PatternType; + + fn index(&self, index: i32) -> &Self::Output { + std::ops::Index::::index(self, index as isize) } } @@ -283,7 +324,7 @@ impl std::ops::IndexMut for BarAndSpace // bool isValid() const { return bar != T{} && space != T{}; } // }; -type BarAndSpaceI = BarAndSpace; +type BarAndSpaceI = BarAndSpace; /** * @brief FixedPattern describes a compile-time constant (start/stop) pattern. @@ -293,15 +334,19 @@ type BarAndSpaceI = BarAndSpace; * @param IS_SPARCE whether or not the pattern contains '0's denoting 'wide' bars/spaces */ pub struct FixedPattern { - data: [u16; N], + data: [PatternType; N], } impl FixedPattern { - fn new(data: [u16; N]) -> Self { + pub fn new(data: [PatternType; N]) -> Self { FixedPattern { data } } - fn as_slice(&self) -> &[u16] { + pub fn with_reference(data: &[PatternType; N]) -> Self { + FixedPattern { data: data.clone() } + } + + fn as_slice(&self) -> &[PatternType] { &self.data } @@ -313,7 +358,7 @@ impl FixedPattern std::ops::Index for FixedPattern { - type Output = u16; + type Output = PatternType; fn index(&self, index: usize) -> &Self::Output { &self.data[index] @@ -419,10 +464,6 @@ pub fn FindLeftGuardBy<'a, const LEN: usize, Pred: Fn(&PatternView, Option) window.skipPair(); } - // for (auto end = view.end() - minSize; window.data() < end; window.skipPair()) - // { - - // } Err(Exceptions::ILLEGAL_STATE) } @@ -483,7 +524,7 @@ pub fn NormalizedPattern<'a, const LEN: usize, const SUM: usize>( .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) }; let mi = mi.ok_or(Exceptions::ILLEGAL_STATE)?; - is[*mi as usize] += err as u16; + is[*mi as usize] += err as PatternType; rs[*mi as usize] -= err as f32; } @@ -496,17 +537,16 @@ enum Color { Black = 1 } -impl> From for Color { +impl> From for Color { fn from(value: T) -> Self { match value.into() { 0 => Color::White, _ => Color::Black, - _=>Color::Black } } } -fn GetPatternRow + Copy + Default + From>(b_row: &[T], p_row: &mut PatternRow) { +fn GetPatternRow + Copy + Default + From>(b_row: &[T], p_row: &mut PatternRow) { p_row.0.clear(); if Color::from(p_row.0.first().copied().unwrap_or_default()) == Color::Black { @@ -542,20 +582,22 @@ fn GetPatternRow + Copy + Default + From>(b_row: &[T], p_row: &m #[cfg(test)] mod tests { - use super::{GetPatternRow, PatternRow}; + use crate::common::cpp_essentials::PatternType; + + use super::{GetPatternRow, PatternRow, PatternView}; const N: usize = 33; #[test] fn all_white() { for s in 1..=N { // for (int s = 1; s <= N; ++s) { - let t_in = vec![0_u16; s]; + let t_in:Vec = vec![0; s]; // std::vector in(s, 0); let mut pr = PatternRow::default(); GetPatternRow(&t_in, &mut pr); assert_eq!(pr.0.len(), 1); - assert_eq!(pr.0[0], s as u16); + assert_eq!(pr.0[0], s as PatternType); } } @@ -563,13 +605,13 @@ mod tests { fn all_black() { for s in 1..=N { // for (int s = 1; s <= N; ++s) { - let t_in: Vec = vec![0xff; s]; + let t_in: Vec = vec![0xff; s]; let mut pr = PatternRow::default(); GetPatternRow(&t_in, &mut pr); assert_eq!(pr.0.len(), 3); assert_eq!(pr.0[0], 0); - assert_eq!(pr.0[1], s as u16); + assert_eq!(pr.0[1], s as PatternType); assert_eq!(pr.0[2], 0); } } @@ -578,7 +620,7 @@ mod tests { fn black_white() { for s in 1..=N { // for (int s = 1; s <= N; ++s) { - let mut t_in = vec![0_u16; N]; + let mut t_in : Vec = vec![0; N]; t_in[..s].copy_from_slice(&vec![1; s]); // std::fill_n(in.data(), s, 0xff); let mut pr = PatternRow::default(); @@ -586,8 +628,8 @@ mod tests { assert_eq!(pr.0.len(), 3); assert_eq!(pr.0[0], 0); - assert_eq!(pr.0[1], s as u16); - assert_eq!(pr.0[2], (N - s) as u16); + assert_eq!(pr.0[1], s as PatternType); + assert_eq!(pr.0[2], (N - s) as PatternType); } } @@ -595,15 +637,36 @@ mod tests { fn white_black() { for s in 0..N { // for (int s = 0; s < N; ++s) { - let mut t_in: Vec = vec![0xff; N]; + let mut t_in: Vec = vec![0xff; N]; t_in[..s].copy_from_slice(&vec![0; s]); let mut pr = PatternRow::default(); GetPatternRow(&t_in, &mut pr); assert_eq!(pr.0.len(), 3); - assert_eq!(pr.0[0], s as u16); - assert_eq!(pr.0[1], (N - s) as u16); + assert_eq!(pr.0[0], s as PatternType); + assert_eq!(pr.0[1], (N - s) as PatternType); assert_eq!(pr.0[2], 0); } } + + #[test] + fn basic_pattern_view() { + let mut p_row = PatternRow::default(); + GetPatternRow(&vec![0_u16,1,0,1,0,0,1,1,1,0,0,1,1,1,1,1,1,0,0,0,0,1], &mut p_row); + + let mut pv = PatternView::new(&p_row); + + assert_eq!(pv.data().0,p_row.0); + + assert_eq!(pv[0], 1_u16); + assert_eq!(pv[1], 1_u16); + assert_eq!(pv[4], 2_u16); + assert_eq!(pv[7], 6_u16); + + assert_eq!(pv.index(), 0); + assert!(pv.shift(1)); + assert_eq!(pv.index(), 1); + assert!(pv.skipPair()); + assert_eq!(pv.index(),3); + } } diff --git a/src/common/cpp_essentials/regression_line.rs b/src/common/cpp_essentials/regression_line.rs index a9ccd90..a859480 100644 --- a/src/common/cpp_essentials/regression_line.rs +++ b/src/common/cpp_essentials/regression_line.rs @@ -1,5 +1,5 @@ use crate::common::Result; -use crate::Point; +use crate::{Point, point}; pub trait RegressionLine { // points: Vec, @@ -11,8 +11,18 @@ pub trait RegressionLine { // PointF _directionInward; // PointF::value_t a = NAN, b = NAN, c = NAN; - // fn intersect(&self, l1: &T, l2: &T2) - // -> Point; + fn intersect( l1: &T, l2: &T2) + -> Option{ + if !(l1.isValid() && l2.isValid()) { + return None; + } + + let d = l1.a() * l2.b() - l1.b() * l2.a(); + let x = (l1.c() * l2.b() - l1.b() * l2.c()) / d; + let y = (l1.a() * l2.c() - l1.c() * l2.a()) / d; + + Some(point(x, y)) + } // fn evaluate_begin_end(&self, begin: Point, end: Point) -> bool;// { // { @@ -131,4 +141,7 @@ pub trait RegressionLine { // // due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal // return steps > 2 || len > 50; // } + fn a(&self) -> f32; + fn b(&self) -> f32; + fn c(&self) -> f32; } diff --git a/src/common/quad.rs b/src/common/quad.rs index 688a1dd..3d33ad2 100644 --- a/src/common/quad.rs +++ b/src/common/quad.rs @@ -208,6 +208,28 @@ impl Quadrilateral { !(x || y) } + + pub fn blend(a: &Quadrilateral, b:&Quadrilateral) -> Self { + let c = a[0]; + let dist2First = | a, b| { Point::distance(a, c) < Point::distance(b, c) }; + // rotate points such that the the two topLeft points are closest to each other + let min_element = b.0.iter().copied().min_by(|a,b| { + match dist2First(*a,*b) { + true => std::cmp::Ordering::Less, + false => std::cmp::Ordering::Greater, + } + }).unwrap_or_default(); + let offset = b.0.iter().position(|v| *v == min_element).unwrap_or_default(); + // let offset = std::min_element(b.begin(), b.end(), dist2First) - b.begin(); + + let mut res= Quadrilateral::default(); + for i in 0..4 { + // for (int i = 0; i < 4; ++i){ + res[i] = (a[i] + b[(i + offset) % 4]) / 2.0; + } + + res + } } impl Default for Quadrilateral { @@ -215,3 +237,17 @@ impl Default for Quadrilateral { Self([Point { x: 0.0, y: 0.0 }; 4]) } } + +impl std::ops::Index for Quadrilateral { + type Output = Point; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + +impl std::ops::IndexMut for Quadrilateral { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} \ No newline at end of file diff --git a/src/rxing_result_point.rs b/src/rxing_result_point.rs index 78695c7..da5e401 100644 --- a/src/rxing_result_point.rs +++ b/src/rxing_result_point.rs @@ -117,6 +117,22 @@ impl std::ops::Add for Point { } } +impl std::ops::Add for Point { + type Output = Self; + + fn add(self, rhs: f32) -> Self::Output { + Self::new(self.x + rhs, self.y + rhs) + } +} + +impl std::ops::Add for f32 { + type Output = Point; + + fn add(self, rhs: Point) -> Self::Output { + Point::new(rhs.x + self, rhs.y + self) + } +} + impl std::ops::Mul for Point { type Output = Self; @@ -141,6 +157,14 @@ impl std::ops::Mul for Point { } } +impl std::ops::Mul for Point { + type Output = Self; + + fn mul(self, rhs: u32) -> Self::Output { + Self::new(self.x * rhs as f32, self.y * rhs as f32) + } +} + impl std::ops::Mul for i32 { type Output = Point; @@ -165,6 +189,14 @@ impl std::ops::Div for Point { } } +impl std::ops::Mul for u32 { + type Output = Point; + + fn mul(self, rhs: Point) -> Self::Output { + Self::Output::new(rhs.x * self as f32, rhs.y * self as f32) + } +} + impl Point { pub fn dot(self, p: Self) -> f32 { self.x * p.x + self.y * p.y From 3c575ed2d3d794696818a69a670f823dd7670a6e Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sun, 12 Mar 2023 11:49:33 -0500 Subject: [PATCH 07/72] port concentric finder (no test) --- src/common/cpp_essentials/bitmatrix_cursor.rs | 12 +- .../cpp_essentials/concentric_finder.rs | 90 +++-- .../cpp_essentials/dm_regression_line.rs | 25 +- src/common/cpp_essentials/edge_tracer.rs | 17 +- .../fast_edge_to_edge_counter.rs | 48 ++- src/common/cpp_essentials/mod.rs | 6 +- src/common/cpp_essentials/pattern.rs | 41 ++- src/common/cpp_essentials/regression_line.rs | 334 +++++++++++------- .../cpp_essentials/regression_line_trait.rs | 149 ++++++++ src/common/cpp_essentials/util.rs | 31 +- src/common/quad.rs | 41 ++- .../zxing_cpp_detector/cpp_new_detector.rs | 7 +- 12 files changed, 524 insertions(+), 277 deletions(-) create mode 100644 src/common/cpp_essentials/regression_line_trait.rs diff --git a/src/common/cpp_essentials/bitmatrix_cursor.rs b/src/common/cpp_essentials/bitmatrix_cursor.rs index 5cf6950..2f059a0 100644 --- a/src/common/cpp_essentials/bitmatrix_cursor.rs +++ b/src/common/cpp_essentials/bitmatrix_cursor.rs @@ -78,12 +78,12 @@ pub trait BitMatrixCursor { // } fn movedBy(self, d: Point) -> Self; - fn turnedBack(&self) -> Self;// { return {*img, p, back()}; } - // { - // auto res = *this; - // res.p += d; - // return res; - // } + fn turnedBack(&self) -> Self; // { return {*img, p, back()}; } + // { + // auto res = *this; + // res.p += d; + // return res; + // } /** * @brief stepToEdge advances cursor to one step behind the next (or n-th) edge. diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index 6fffe69..04cddd4 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -9,7 +9,8 @@ use crate::{ }; use super::{ - BitMatrixCursor, DMRegressionLine, EdgeTracer, FastEdgeToEdgeCounter, Pattern, RegressionLine, + BitMatrixCursor, EdgeTracer, FastEdgeToEdgeCounter, Pattern, RegressionLine, + RegressionLineTrait, }; pub fn CenterFromEnd + std::iter::Sum + Copy>( @@ -378,7 +379,7 @@ pub fn FitQadrilateralToPoints(center: Point, points: &mut [Point]) -> Option Option Option( image:&BitMatrix, pattern:&Pattern, center:Point, range:i32) -> Option -{ - let mut cur = EdgeTracer::new(image, center, Point::default()); - let mut minSpread = image.getWidth() as i32; +pub fn LocateConcentricPattern< + const RELAXED_THRESHOLD: bool, + const LEN: usize, + const SUM: usize, + T: BitMatrixCursor, +>( + image: &BitMatrix, + pattern: &Pattern, + center: Point, + range: i32, +) -> Option { + let mut cur = EdgeTracer::new(image, center, Point::default()); + let mut minSpread = image.getWidth() as i32; let mut maxSpread = 0_i32; - for d in [point(0.0,1.0), point(1.0,0.0)] { - // for (auto d : {PointI{0, 1}, {1, 0}}) { + for d in [point(0.0, 1.0), point(1.0, 0.0)] { + // for (auto d : {PointI{0, 1}, {1, 0}}) { cur.setDirection(d); // THIS COULD POSSIBLY BE WRONG, WE MIGHT MEAN TO CLONE cur EACH RUN? - let spread = CheckSymmetricPattern(&mut cur, pattern, range, true); - if (!(spread != 0)) - {return None} - UpdateMinMax(&mut minSpread, &mut maxSpread, spread); - } + let spread = + CheckSymmetricPattern::(&mut cur, pattern, range, true); + if (!(spread != 0)) { + return None; + } + UpdateMinMax(&mut minSpread, &mut maxSpread, spread); + } -//#if 1 -for d in [point(1.0,1.0), point(1.0,-1.0)] { - // for (auto d : {PointI{1, 1}, {1, -1}}) { - cur.setDirection(d);// THIS COULD POSSIBLY BE WRONG, WE MIGHT MEAN TO CLONE cur EACH RUN? - let spread = CheckSymmetricPattern(&mut cur, pattern, range * 2, false); - if (!(spread != 0)) - {return None} - UpdateMinMax(&mut minSpread, &mut maxSpread, spread); - } -//#endif + //#if 1 + for d in [point(1.0, 1.0), point(1.0, -1.0)] { + // for (auto d : {PointI{1, 1}, {1, -1}}) { + cur.setDirection(d); // THIS COULD POSSIBLY BE WRONG, WE MIGHT MEAN TO CLONE cur EACH RUN? + let spread = CheckSymmetricPattern::( + &mut cur, + pattern, + range * 2, + false, + ); + if (!(spread != 0)) { + return None; + } + UpdateMinMax(&mut minSpread, &mut maxSpread, spread); + } + //#endif - if (maxSpread > 5 * minSpread) - {return None} + if (maxSpread > 5 * minSpread) { + return None; + } - let newCenter = FinetuneConcentricPatternCenter(image, cur.p(), range, pattern.len() as u32)?; + let newCenter = FinetuneConcentricPatternCenter(image, cur.p(), range, pattern.len() as u32)?; - Some(ConcentricPattern{*newCenter, (maxSpread + minSpread) / 2}) + Some(ConcentricPattern { + p: newCenter, + size: (maxSpread + minSpread) / 2, + }) } fn UpdateMinMax(min: &mut T, max: &mut T, val: T) { diff --git a/src/common/cpp_essentials/dm_regression_line.rs b/src/common/cpp_essentials/dm_regression_line.rs index 8a574bd..dfb7cf0 100644 --- a/src/common/cpp_essentials/dm_regression_line.rs +++ b/src/common/cpp_essentials/dm_regression_line.rs @@ -1,10 +1,7 @@ use crate::common::Result; -use crate::{Exceptions, Point, point}; +use crate::{Exceptions, Point}; -use super::{ - util::{float_max, float_min}, - RegressionLine, -}; +use super::RegressionLineTrait; #[derive(Clone)] pub struct DMRegressionLine { @@ -30,7 +27,7 @@ impl Default for DMRegressionLine { } } -impl RegressionLine for DMRegressionLine { +impl RegressionLineTrait for DMRegressionLine { fn points(&self) -> &[Point] { &self.points } @@ -136,14 +133,14 @@ impl RegressionLine for DMRegressionLine { let Some(mut min) = self.points.first().copied() else { return false }; let Some(mut max) = self.points.first().copied() else { return false }; for p in &self.points { - min.x = float_min(min.x, p.x); - min.y = float_min(min.y, p.y); - max.x = float_max(max.x, p.x); - max.y = float_max(max.y, p.y); + min.x = f32::min(min.x, p.x); + min.y = f32::min(min.y, p.y); + max.x = f32::max(max.x, p.x); + max.y = f32::max(max.y, p.y); } let diff = max - min; let len = diff.maxAbsComponent(); - let steps = float_min(diff.x.abs(), diff.y.abs()); + let steps = f32::min(diff.x.abs(), diff.y.abs()); // due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal steps > 2.0 || len > 50.0 } @@ -226,12 +223,6 @@ impl RegressionLine for DMRegressionLine { } impl DMRegressionLine { - pub fn with_two_points(point1: Point, point2: Point) -> Self { - let mut new_rl = DMRegressionLine::default(); - new_rl.evaluate(&[point1, point2]); - new_rl - } - // template fn average(c: &[f64], f: T) -> f64 where diff --git a/src/common/cpp_essentials/edge_tracer.rs b/src/common/cpp_essentials/edge_tracer.rs index 0f78223..b91b09f 100644 --- a/src/common/cpp_essentials/edge_tracer.rs +++ b/src/common/cpp_essentials/edge_tracer.rs @@ -6,7 +6,7 @@ use crate::{ Exceptions, Point, }; -use super::{BitMatrixCursor, Direction, RegressionLine, StepResult, Value}; +use super::{BitMatrixCursor, Direction, RegressionLineTrait, StepResult, Value}; #[derive(Clone)] pub struct EdgeTracer<'a> { @@ -142,9 +142,9 @@ impl BitMatrixCursor for EdgeTracer<'_> { */ fn stepToEdge(&mut self, nth: Option, range: Option, backup: Option) -> i32 { let mut nth = nth.unwrap_or(1); //if let Some(nth) = nth { nth } else { 1 }; - let range = range.unwrap_or(0);//if let Some(r) = range { r } else { 0 }; - let backup = backup.unwrap_or(false);//if let Some(b) = backup { b } else { false }; - // TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt() + let range = range.unwrap_or(0); //if let Some(r) = range { r } else { 0 }; + let backup = backup.unwrap_or(false); //if let Some(b) = backup { b } else { false }; + // TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt() let mut steps = 0; let mut lv = self.testAt(self.p); @@ -166,7 +166,6 @@ impl BitMatrixCursor for EdgeTracer<'_> { fn p(&self) -> Point { self.p } - } impl<'a> EdgeTracer<'_> { @@ -271,7 +270,11 @@ impl<'a> EdgeTracer<'_> { true } - pub fn traceLine(&mut self, dEdge: Point, line: &mut T) -> Result { + pub fn traceLine( + &mut self, + dEdge: Point, + line: &mut T, + ) -> Result { line.setDirectionInward(dEdge); loop { // log(self.p); @@ -298,7 +301,7 @@ impl<'a> EdgeTracer<'_> { } // while (true); } - pub fn traceGaps( + pub fn traceGaps( &mut self, dEdge: Point, line: &mut T, diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index 835cb2a..cd0b7b3 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -2,39 +2,37 @@ use super::BitMatrixCursor; pub struct FastEdgeToEdgeCounter { // const uint8_t* p = nullptr; - // int stride = 0; - // int stepsToBorder = 0; + // int stride = 0; + // int stepsToBorder = 0; } -impl FastEdgeToEdgeCounter -{ +impl FastEdgeToEdgeCounter { pub fn new(cur: &T) -> Self { todo!() // stride = cur.d.y * cur.img->width() + cur.d.x; - // p = cur.img->row(cur.p.y).begin() + cur.p.x; + // p = cur.img->row(cur.p.y).begin() + cur.p.x; - // int maxStepsX = cur.d.x ? (cur.d.x > 0 ? cur.img->width() - 1 - cur.p.x : cur.p.x) : INT_MAX; - // int maxStepsY = cur.d.y ? (cur.d.y > 0 ? cur.img->height() - 1 - cur.p.y : cur.p.y) : INT_MAX; - // stepsToBorder = std::min(maxStepsX, maxStepsY); + // int maxStepsX = cur.d.x ? (cur.d.x > 0 ? cur.img->width() - 1 - cur.p.x : cur.p.x) : INT_MAX; + // int maxStepsY = cur.d.y ? (cur.d.y > 0 ? cur.img->height() - 1 - cur.p.y : cur.p.y) : INT_MAX; + // stepsToBorder = std::min(maxStepsX, maxStepsY); } - pub fn stepToNextEdge(&self, range: i32) -> i32 - { + pub fn stepToNextEdge(&self, range: i32) -> i32 { todo!() - // int maxSteps = std::min(stepsToBorder, range); - // int steps = 0; - // do { - // if (++steps > maxSteps) { - // if (maxSteps == stepsToBorder) - // break; - // else - // return 0; - // } - // } while (p[steps * stride] == p[0]); + // int maxSteps = std::min(stepsToBorder, range); + // int steps = 0; + // do { + // if (++steps > maxSteps) { + // if (maxSteps == stepsToBorder) + // break; + // else + // return 0; + // } + // } while (p[steps * stride] == p[0]); - // p += steps * stride; - // stepsToBorder -= steps; + // p += steps * stride; + // stepsToBorder -= steps; - // return steps; - } -} \ No newline at end of file + // return steps; + } +} diff --git a/src/common/cpp_essentials/mod.rs b/src/common/cpp_essentials/mod.rs index 3f040c0..725388d 100644 --- a/src/common/cpp_essentials/mod.rs +++ b/src/common/cpp_essentials/mod.rs @@ -3,21 +3,23 @@ pub mod concentric_finder; pub mod direction; pub mod dm_regression_line; pub mod edge_tracer; +pub mod fast_edge_to_edge_counter; pub mod pattern; pub mod regression_line; +pub mod regression_line_trait; pub mod step_result; pub mod util; pub mod value; -pub mod fast_edge_to_edge_counter; pub use bitmatrix_cursor::*; pub use concentric_finder::*; pub use direction::*; pub use dm_regression_line::*; pub use edge_tracer::*; +pub use fast_edge_to_edge_counter::*; pub use pattern::*; pub use regression_line::*; +pub use regression_line_trait::*; pub use step_result::*; pub use util::*; pub use value::*; -pub use fast_edge_to_edge_counter::*; diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index ff5cf36..3b6d82c 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -8,19 +8,19 @@ use crate::{common::Result, Exceptions}; pub type PatternType = u16; pub type Pattern = [PatternType; N]; -#[derive(Default,Debug)] +#[derive(Default, Debug)] pub struct PatternRow(Vec); // pub struct PatternRow + Into + Copy>(Vec); impl PatternRow { -pub fn new( v: Vec) -> Self { - Self(v) -} + pub fn new(v: Vec) -> Self { + Self(v) + } -pub fn len(&self) -> usize { - self.0.len() -} + pub fn len(&self) -> usize { + self.0.len() + } pub fn into_pattern_view(&self) -> PatternView { PatternView::new(self) @@ -534,7 +534,7 @@ pub fn NormalizedPattern<'a, const LEN: usize, const SUM: usize>( #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum Color { White = 0, - Black = 1 + Black = 1, } impl> From for Color { @@ -546,11 +546,14 @@ impl> From for Color { } } -fn GetPatternRow + Copy + Default + From>(b_row: &[T], p_row: &mut PatternRow) { +fn GetPatternRow + Copy + Default + From>( + b_row: &[T], + p_row: &mut PatternRow, +) { p_row.0.clear(); if Color::from(p_row.0.first().copied().unwrap_or_default()) == Color::Black { - // first + // first p_row.0.push(0); } @@ -567,7 +570,7 @@ fn GetPatternRow + Copy + Default + From>(b_row: &[T], p current_color = this_color; } - count +=1 ; + count += 1; } if count != 0 { @@ -577,7 +580,6 @@ fn GetPatternRow + Copy + Default + From>(b_row: &[T], p if current_color == Color::Black { p_row.0.push(0); } - } #[cfg(test)] @@ -591,7 +593,7 @@ mod tests { fn all_white() { for s in 1..=N { // for (int s = 1; s <= N; ++s) { - let t_in:Vec = vec![0; s]; + let t_in: Vec = vec![0; s]; // std::vector in(s, 0); let mut pr = PatternRow::default(); GetPatternRow(&t_in, &mut pr); @@ -620,7 +622,7 @@ mod tests { fn black_white() { for s in 1..=N { // for (int s = 1; s <= N; ++s) { - let mut t_in : Vec = vec![0; N]; + let mut t_in: Vec = vec![0; N]; t_in[..s].copy_from_slice(&vec![1; s]); // std::fill_n(in.data(), s, 0xff); let mut pr = PatternRow::default(); @@ -652,11 +654,16 @@ mod tests { #[test] fn basic_pattern_view() { let mut p_row = PatternRow::default(); - GetPatternRow(&vec![0_u16,1,0,1,0,0,1,1,1,0,0,1,1,1,1,1,1,0,0,0,0,1], &mut p_row); + GetPatternRow( + &vec![ + 0_u16, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + ], + &mut p_row, + ); let mut pv = PatternView::new(&p_row); - assert_eq!(pv.data().0,p_row.0); + assert_eq!(pv.data().0, p_row.0); assert_eq!(pv[0], 1_u16); assert_eq!(pv[1], 1_u16); @@ -667,6 +674,6 @@ mod tests { assert!(pv.shift(1)); assert_eq!(pv.index(), 1); assert!(pv.skipPair()); - assert_eq!(pv.index(),3); + assert_eq!(pv.index(), 3); } } diff --git a/src/common/cpp_essentials/regression_line.rs b/src/common/cpp_essentials/regression_line.rs index a859480..8847389 100644 --- a/src/common/cpp_essentials/regression_line.rs +++ b/src/common/cpp_essentials/regression_line.rs @@ -1,147 +1,231 @@ use crate::common::Result; -use crate::{Point, point}; +use crate::{Exceptions, Point}; -pub trait RegressionLine { - // points: Vec, - // direction_inward: Point, +use super::RegressionLineTrait; - // } - // impl RegressionLine { +#[derive(Clone)] +pub struct RegressionLine { + points: Vec, + direction_inward: Point, + pub(super) a: f32, + pub(super) b: f32, + pub(super) c: f32, // std::vector _points; // PointF _directionInward; // PointF::value_t a = NAN, b = NAN, c = NAN; +} - fn intersect( l1: &T, l2: &T2) - -> Option{ - if !(l1.isValid() && l2.isValid()) { - return None; - } - - let d = l1.a() * l2.b() - l1.b() * l2.a(); - let x = (l1.c() * l2.b() - l1.b() * l2.c()) / d; - let y = (l1.a() * l2.c() - l1.c() * l2.a()) / d; - - Some(point(x, y)) +impl Default for RegressionLine { + fn default() -> Self { + Self { + points: Default::default(), + direction_inward: Default::default(), + a: f32::NAN, + b: f32::NAN, + c: f32::NAN, } + } +} - // fn evaluate_begin_end(&self, begin: Point, end: Point) -> bool;// { - // { - // let mean = std::accumulate(begin, end, PointF()) / std::distance(begin, end); - // PointF::value_t sumXX = 0, sumYY = 0, sumXY = 0; - // for (auto p = begin; p != end; ++p) { - // auto d = *p - mean; - // sumXX += d.x * d.x; - // sumYY += d.y * d.y; - // sumXY += d.x * d.y; - // } - // if (sumYY >= sumXX) { - // auto l = std::sqrt(sumYY * sumYY + sumXY * sumXY); - // a = +sumYY / l; - // b = -sumXY / l; - // } else { - // auto l = std::sqrt(sumXX * sumXX + sumXY * sumXY); - // a = +sumXY / l; - // b = -sumXX / l; - // } - // if (dot(_directionInward, normal()) < 0) { - // a = -a; - // b = -b; - // } - // c = dot(normal(), mean); // (a*mean.x + b*mean.y); - // return dot(_directionInward, normal()) > 0.5f; // angle between original and new direction is at most 60 degree - // } - - fn evaluate(&mut self, points: &[Point]) -> bool; // { return self.evaluate_begin_end(&points.front(), &points.back() + 1); } - fn evaluateSelf(&mut self) -> bool; - - // RegressionLine() { _points.reserve(16); } // arbitrary but plausible start size (tiny performance improvement) - - // template RegressionLine(PointT a, PointT b) - // { - // evaluate(std::vector{a, b}); - // } - - // template RegressionLine(const PointT* b, const PointT* e) - // { - // evaluate(b, e); - // } - - fn points(&self) -> &[Point]; //const { return _points; } - fn length(&self) -> u32; //const { return _points.size() >= 2 ? int(distance(_points.front(), _points.back())) : 0; } - fn isValid(&self) -> bool; //const { return !std::isnan(a); } - fn normal(&self) -> Point; //const { return isValid() ? PointF(a, b) : _directionInward; } - fn signedDistance(&self, p: Point) -> f32; //const { return dot(normal(), p) - c; } - fn distance_single(&self, p: Point) -> f32; //const { return std::abs(signedDistance(PointF(p))); } - fn project(&self, p: Point) -> Point { - p - self.normal() * self.signedDistance(p) +impl RegressionLineTrait for RegressionLine { + fn points(&self) -> &[Point] { + &self.points } - fn reset(&mut self); - // { - // _points.clear(); - // _directionInward = {}; - // a = b = c = NAN; - // } + fn length(&self) -> u32 { + if self.points.len() >= 2 { + Point::distance(*self.points.first().unwrap(), *self.points.last().unwrap()) as u32 + } else { + 0 + } + } - fn add(&mut self, p: Point) -> Result<()>; //{ - // assert(_directionInward != PointF()); - // _points.push_back(p); - // if (_points.size() == 1) - // c = dot(normal(), p); - // } + fn isValid(&self) -> bool { + !self.a.is_nan() + } - fn pop_back(&mut self); // { _points.pop_back(); } + fn normal(&self) -> Point { + if self.isValid() { + Point { + x: self.a, + y: self.b, + } + } else { + self.direction_inward + } + } - fn setDirectionInward(&mut self, d: Point); //{ _directionInward = normalized(d); } + fn signedDistance(&self, p: Point) -> f32 { + Point::dot(self.normal(), p) - self.c + } + + fn distance_single(&self, p: Point) -> f32 { + (self.signedDistance(p)).abs() + } + + fn reset(&mut self) { + self.points.clear(); + self.direction_inward = Point { x: 0.0, y: 0.0 }; + self.a = f32::NAN; + self.b = f32::NAN; + self.c = f32::NAN; + } + + fn add(&mut self, p: Point) -> Result<()> { + if self.direction_inward == Point::default() { + return Err(Exceptions::ILLEGAL_STATE); + } + self.points.push(p); + if self.points.len() == 1 { + self.c = Point::dot(self.normal(), p); + } + Ok(()) + } + + fn pop_back(&mut self) { + self.points.pop(); + } + + fn setDirectionInward(&mut self, d: Point) { + self.direction_inward = Point::normalized(d); + } - // fn evaluate(&self, double maxSignedDist = -1, bool updatePoints = false) -> bool fn evaluate_max_distance( &mut self, maxSignedDist: Option, updatePoints: Option, - ) -> bool; - // { - // bool ret = evaluate(_points); - // if (maxSignedDist > 0) { - // auto points = _points; - // while (true) { - // auto old_points_size = points.size(); - // // remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist - // auto end = std::remove_if(points.begin(), points.end(), [this, maxSignedDist](auto p) { - // auto sd = this->signedDistance(p); - // return sd > maxSignedDist || sd < -2 * maxSignedDist; - // }); - // points.erase(end, points.end()); - // if (old_points_size == points.size()) - // break; - // // #ifdef PRINT_DEBUG - // // printf("removed %zu points\n", old_points_size - points.size()); - // // #endif - // ret = evaluate(points); - // } + ) -> bool { + let maxSignedDist = if let Some(m) = maxSignedDist { m } else { -1.0 }; + let updatePoints = if let Some(u) = updatePoints { u } else { false }; - // if (updatePoints) - // _points = std::move(points); - // } - // return ret; - // } + let mut ret = self.evaluateSelf(); + if maxSignedDist > 0.0 { + let mut points = self.points.clone(); + loop { + let old_points_size = points.len(); + // remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist + // auto end = std::remove_if(points.begin(), points.end(), [this, maxSignedDist](auto p) { + // auto sd = this->signedDistance(p); + // return sd > maxSignedDist || sd < -2 * maxSignedDist; + // }); + // points.erase(end, points.end()); + points.retain(|&p| { + let sd = self.signedDistance(p) as f64; + !(sd > maxSignedDist || sd < -2.0 * maxSignedDist) + }); + if old_points_size == points.len() { + break; + } + // #ifdef PRINT_DEBUG + // printf("removed %zu points\n", old_points_size - points.size()); + // #endif + ret = self.evaluate(&points); + } - fn isHighRes(&self) -> bool; //const - // { - // PointF min = _points.front(), max = _points.front(); - // for (auto p : _points) { - // min.x = std::min(min.x, p.x); - // min.y = std::min(min.y, p.y); - // max.x = std::max(max.x, p.x); - // max.y = std::max(max.y, p.y); - // } - // auto diff = max - min; - // auto len = maxAbsComponent(diff); - // auto steps = std::min(std::abs(diff.x), std::abs(diff.y)); - // // due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal - // return steps > 2 || len > 50; - // } - fn a(&self) -> f32; - fn b(&self) -> f32; - fn c(&self) -> f32; + if updatePoints { + self.points = points; + } + } + ret + } + + fn isHighRes(&self) -> bool { + let Some(mut min) = self.points.first().copied() else { return false }; + let Some(mut max) = self.points.first().copied() else { return false }; + for p in &self.points { + min.x = f32::min(min.x, p.x); + min.y = f32::min(min.y, p.y); + max.x = f32::max(max.x, p.x); + max.y = f32::max(max.y, p.y); + } + let diff = max - min; + let len = diff.maxAbsComponent(); + let steps = f32::min(diff.x.abs(), diff.y.abs()); + // due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal + steps > 2.0 || len > 50.0 + } + + fn evaluate(&mut self, points: &[Point]) -> bool { + let mean = points.iter().sum::() / points.len() as f32; + + let mut sumXX = 0.0; + let mut sumYY = 0.0; + let mut sumXY = 0.0; + for p in points { + // for (auto p = begin; p != end; ++p) { + let d = *p - mean; + sumXX += d.x * d.x; + sumYY += d.y * d.y; + sumXY += d.x * d.y; + } + if sumYY >= sumXX { + let l = (sumYY * sumYY + sumXY * sumXY).sqrt(); + self.a = sumYY / l; + self.b = -sumXY / l; + } else { + let l = (sumXX * sumXX + sumXY * sumXY).sqrt(); + self.a = sumXY / l; + self.b = -sumXX / l; + } + if Point::dot(self.direction_inward, self.normal()) < 0.0 { + // if (dot(_directionInward, normal()) < 0) { + self.a = -self.a; + self.b = -self.b; + } + self.c = Point::dot(self.normal(), mean); // (a*mean.x + b*mean.y); + Point::dot(self.direction_inward, self.normal()) > 0.5 + // angle between original and new direction is at most 60 degree + } + + fn evaluateSelf(&mut self) -> bool { + let mean = self.points.iter().sum::() / self.points.len() as f32; + + let mut sumXX = 0.0; + let mut sumYY = 0.0; + let mut sumXY = 0.0; + for p in &self.points { + // for (auto p = begin; p != end; ++p) { + let d = *p - mean; + sumXX += d.x * d.x; + sumYY += d.y * d.y; + sumXY += d.x * d.y; + } + if sumYY >= sumXX { + let l = (sumYY * sumYY + sumXY * sumXY).sqrt(); + self.a = sumYY / l; + self.b = -sumXY / l; + } else { + let l = (sumXX * sumXX + sumXY * sumXY).sqrt(); + self.a = sumXY / l; + self.b = -sumXX / l; + } + if Point::dot(self.direction_inward, self.normal()) < 0.0 { + // if (dot(_directionInward, normal()) < 0) { + self.a = -self.a; + self.b = -self.b; + } + self.c = Point::dot(self.normal(), mean); // (a*mean.x + b*mean.y); + Point::dot(self.direction_inward, self.normal()) > 0.5 + // angle between original and new direction is at most 60 degree + } + + fn a(&self) -> f32 { + self.a + } + + fn b(&self) -> f32 { + self.b + } + + fn c(&self) -> f32 { + self.c + } +} + +impl RegressionLine { + pub fn with_two_points(point1: Point, point2: Point) -> Self { + let mut new_rl = RegressionLine::default(); + new_rl.evaluate(&[point1, point2]); + new_rl + } } diff --git a/src/common/cpp_essentials/regression_line_trait.rs b/src/common/cpp_essentials/regression_line_trait.rs new file mode 100644 index 0000000..4fa8bf3 --- /dev/null +++ b/src/common/cpp_essentials/regression_line_trait.rs @@ -0,0 +1,149 @@ +use crate::common::Result; +use crate::{point, Point}; + +pub trait RegressionLineTrait { + // points: Vec, + // direction_inward: Point, + + // } + // impl RegressionLine { + // std::vector _points; + // PointF _directionInward; + // PointF::value_t a = NAN, b = NAN, c = NAN; + + fn intersect( + l1: &T, + l2: &T2, + ) -> Option { + if !(l1.isValid() && l2.isValid()) { + return None; + } + + let d = l1.a() * l2.b() - l1.b() * l2.a(); + let x = (l1.c() * l2.b() - l1.b() * l2.c()) / d; + let y = (l1.a() * l2.c() - l1.c() * l2.a()) / d; + + Some(point(x, y)) + } + + // fn evaluate_begin_end(&self, begin: Point, end: Point) -> bool;// { + // { + // let mean = std::accumulate(begin, end, PointF()) / std::distance(begin, end); + // PointF::value_t sumXX = 0, sumYY = 0, sumXY = 0; + // for (auto p = begin; p != end; ++p) { + // auto d = *p - mean; + // sumXX += d.x * d.x; + // sumYY += d.y * d.y; + // sumXY += d.x * d.y; + // } + // if (sumYY >= sumXX) { + // auto l = std::sqrt(sumYY * sumYY + sumXY * sumXY); + // a = +sumYY / l; + // b = -sumXY / l; + // } else { + // auto l = std::sqrt(sumXX * sumXX + sumXY * sumXY); + // a = +sumXY / l; + // b = -sumXX / l; + // } + // if (dot(_directionInward, normal()) < 0) { + // a = -a; + // b = -b; + // } + // c = dot(normal(), mean); // (a*mean.x + b*mean.y); + // return dot(_directionInward, normal()) > 0.5f; // angle between original and new direction is at most 60 degree + // } + + fn evaluate(&mut self, points: &[Point]) -> bool; // { return self.evaluate_begin_end(&points.front(), &points.back() + 1); } + fn evaluateSelf(&mut self) -> bool; + + // RegressionLine() { _points.reserve(16); } // arbitrary but plausible start size (tiny performance improvement) + + // template RegressionLine(PointT a, PointT b) + // { + // evaluate(std::vector{a, b}); + // } + + // template RegressionLine(const PointT* b, const PointT* e) + // { + // evaluate(b, e); + // } + + fn points(&self) -> &[Point]; //const { return _points; } + fn length(&self) -> u32; //const { return _points.size() >= 2 ? int(distance(_points.front(), _points.back())) : 0; } + fn isValid(&self) -> bool; //const { return !std::isnan(a); } + fn normal(&self) -> Point; //const { return isValid() ? PointF(a, b) : _directionInward; } + fn signedDistance(&self, p: Point) -> f32; //const { return dot(normal(), p) - c; } + fn distance_single(&self, p: Point) -> f32; //const { return std::abs(signedDistance(PointF(p))); } + fn project(&self, p: Point) -> Point { + p - self.normal() * self.signedDistance(p) + } + + fn reset(&mut self); + // { + // _points.clear(); + // _directionInward = {}; + // a = b = c = NAN; + // } + + fn add(&mut self, p: Point) -> Result<()>; //{ + // assert(_directionInward != PointF()); + // _points.push_back(p); + // if (_points.size() == 1) + // c = dot(normal(), p); + // } + + fn pop_back(&mut self); // { _points.pop_back(); } + + fn setDirectionInward(&mut self, d: Point); //{ _directionInward = normalized(d); } + + // fn evaluate(&self, double maxSignedDist = -1, bool updatePoints = false) -> bool + fn evaluate_max_distance( + &mut self, + maxSignedDist: Option, + updatePoints: Option, + ) -> bool; + // { + // bool ret = evaluate(_points); + // if (maxSignedDist > 0) { + // auto points = _points; + // while (true) { + // auto old_points_size = points.size(); + // // remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist + // auto end = std::remove_if(points.begin(), points.end(), [this, maxSignedDist](auto p) { + // auto sd = this->signedDistance(p); + // return sd > maxSignedDist || sd < -2 * maxSignedDist; + // }); + // points.erase(end, points.end()); + // if (old_points_size == points.size()) + // break; + // // #ifdef PRINT_DEBUG + // // printf("removed %zu points\n", old_points_size - points.size()); + // // #endif + // ret = evaluate(points); + // } + + // if (updatePoints) + // _points = std::move(points); + // } + // return ret; + // } + + fn isHighRes(&self) -> bool; //const + // { + // PointF min = _points.front(), max = _points.front(); + // for (auto p : _points) { + // min.x = std::min(min.x, p.x); + // min.y = std::min(min.y, p.y); + // max.x = std::max(max.x, p.x); + // max.y = std::max(max.y, p.y); + // } + // auto diff = max - min; + // auto len = maxAbsComponent(diff); + // auto steps = std::min(std::abs(diff.x), std::abs(diff.y)); + // // due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal + // return steps > 2 || len > 50; + // } + fn a(&self) -> f32; + fn b(&self) -> f32; + fn c(&self) -> f32; +} diff --git a/src/common/cpp_essentials/util.rs b/src/common/cpp_essentials/util.rs index c2c4180..6b580a1 100644 --- a/src/common/cpp_essentials/util.rs +++ b/src/common/cpp_essentials/util.rs @@ -1,34 +1,19 @@ use crate::common::Result; use crate::{Exceptions, Point}; -use super::{DMRegressionLine, Direction, RegressionLine}; +use super::{Direction, RegressionLineTrait}; #[inline(always)] -pub fn float_min(a: T, b: T) -> T { - if a > b { - b - } else { - a - } -} - -#[inline(always)] -pub fn float_max(a: T, b: T) -> T { - if a < b { - b - } else { - a - } -} - -#[inline(always)] -pub fn intersect(l1: &DMRegressionLine, l2: &DMRegressionLine) -> Result { +pub fn intersect( + l1: &T, + l2: &T2, +) -> Result { if !(l1.isValid() && l2.isValid()) { return Err(Exceptions::ILLEGAL_STATE); } - let d = l1.a * l2.b - l1.b * l2.a; - let x = (l1.c * l2.b - l1.b * l2.c) / d; - let y = (l1.a * l2.c - l1.c * l2.a) / d; + let d = l1.a() * l2.b() - l1.b() * l2.a(); + let x = (l1.c() * l2.b() - l1.b() * l2.c()) / d; + let y = (l1.a() * l2.c() - l1.c() * l2.a()) / d; Ok(Point { x, y }) } diff --git a/src/common/quad.rs b/src/common/quad.rs index 3d33ad2..a2b94a6 100644 --- a/src/common/quad.rs +++ b/src/common/quad.rs @@ -209,26 +209,31 @@ impl Quadrilateral { !(x || y) } - pub fn blend(a: &Quadrilateral, b:&Quadrilateral) -> Self { + pub fn blend(a: &Quadrilateral, b: &Quadrilateral) -> Self { let c = a[0]; - let dist2First = | a, b| { Point::distance(a, c) < Point::distance(b, c) }; - // rotate points such that the the two topLeft points are closest to each other - let min_element = b.0.iter().copied().min_by(|a,b| { - match dist2First(*a,*b) { - true => std::cmp::Ordering::Less, - false => std::cmp::Ordering::Greater, + let dist2First = |a, b| Point::distance(a, c) < Point::distance(b, c); + // rotate points such that the the two topLeft points are closest to each other + let min_element = + b.0.iter() + .copied() + .min_by(|a, b| match dist2First(*a, *b) { + true => std::cmp::Ordering::Less, + false => std::cmp::Ordering::Greater, + }) + .unwrap_or_default(); + let offset = + b.0.iter() + .position(|v| *v == min_element) + .unwrap_or_default(); + // let offset = std::min_element(b.begin(), b.end(), dist2First) - b.begin(); + + let mut res = Quadrilateral::default(); + for i in 0..4 { + // for (int i = 0; i < 4; ++i){ + res[i] = (a[i] + b[(i + offset) % 4]) / 2.0; } - }).unwrap_or_default(); - let offset = b.0.iter().position(|v| *v == min_element).unwrap_or_default(); - // let offset = std::min_element(b.begin(), b.end(), dist2First) - b.begin(); - let mut res= Quadrilateral::default(); - for i in 0..4 { - // for (int i = 0; i < 4; ++i){ - res[i] = (a[i] + b[(i + offset) % 4]) / 2.0; - } - - res + res } } @@ -250,4 +255,4 @@ impl std::ops::IndexMut for Quadrilateral { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.0[index] } -} \ No newline at end of file +} diff --git a/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs b/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs index 761cba3..2b98e76 100644 --- a/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs +++ b/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs @@ -14,9 +14,12 @@ macro_rules! CHECK { use std::{cell::RefCell, rc::Rc}; use crate::{ - common::{BitMatrix, DefaultGridSampler, GridSampler, Quadrilateral, Result}, + common::{ + cpp_essentials::RegressionLineTrait, BitMatrix, DefaultGridSampler, GridSampler, + Quadrilateral, Result, + }, datamatrix::detector::{ - zxing_cpp_detector::{util::intersect, BitMatrixCursor, RegressionLine}, + zxing_cpp_detector::{util::intersect, BitMatrixCursor}, DatamatrixDetectorResult, }, point, From 5d6f4b1d94976f951905d9e31f93d999e6cb4fc0 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sun, 12 Mar 2023 11:53:52 -0500 Subject: [PATCH 08/72] clippy --fix --- .../cpp_essentials/concentric_finder.rs | 84 +++++++++---------- .../fast_edge_to_edge_counter.rs | 4 +- src/common/cpp_essentials/pattern.rs | 6 +- 3 files changed, 46 insertions(+), 48 deletions(-) diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index 04cddd4..7758738 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -17,7 +17,7 @@ pub fn CenterFromEnd + std::iter::Sum + Copy>( pattern: &[T; N], end: f32, ) -> f32 { - if (N == 5) { + if N == 5 { let a: f32 = pattern[4].into() + pattern[3].into() + pattern[2].into() / 2.0; let b: f32 = pattern[4].into() + (pattern[3].into() + pattern[2].into() + pattern[1].into()) / 2.0; @@ -28,7 +28,7 @@ pub fn CenterFromEnd + std::iter::Sum + Copy>( + pattern[0].into()) / 2.0; end - (2.0 * a + b + c) / 4.0 - } else if (N == 3) { + } else if N == 3 { let a: f32 = pattern[2].into() + pattern[1].into() / 2.0; let b: f32 = (pattern[2].into() + pattern[1].into() + pattern[0].into()) / 2.0; end - (2.0 * a + b) / 3.0 @@ -59,7 +59,7 @@ pub fn ReadSymmetricPattern( let v = cur.stepToEdge(Some(1), Some(range), None); res[(s_2 + i) as usize] = (res[(s_2 + i) as usize] as i32 + v) as u16; // res[(s_2 + i) as usize] += v; - if (range != 0) { + if range != 0 { range -= v; } @@ -68,7 +68,7 @@ pub fn ReadSymmetricPattern( for i in 0..=s_2 { // for (int i = 0; i <= s_2; ++i) { - if (!next(cur, i) != 0 || !next(&mut cuo, -i) != 0) { + if !next(cur, i) != 0 || !next(&mut cuo, -i) != 0 { return None; } } @@ -95,11 +95,11 @@ pub fn CheckSymmetricPattern< let curBwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(&cur.turnedBack()); let centerFwd = curFwd.stepToNextEdge(range); - if (!(centerFwd != 0)) { + if centerFwd == 0 { return 0; } let centerBwd = curBwd.stepToNextEdge(range); - if (!(centerBwd != 0)) { + if centerBwd == 0 { return 0; } @@ -119,25 +119,25 @@ pub fn CheckSymmetricPattern< for i in 1..=s_2 { // for (int i = 1; i <= s_2; ++i) { - if (!(next(&curFwd, i as isize) != 0) || !(next(&curBwd, -(i as isize)) != 0)) { + if next(&curFwd, i as isize) == 0 || next(&curBwd, -(i as isize)) == 0 { return 0; } } - if (!(IsPattern( + if !(IsPattern( &PatternView::new(&res), &FixedPattern::::with_reference(pattern), None, 0.0, 0.0, Some(RELAXED_THRESHOLD), - ) != 0.0)) + ) != 0.0) { return 0; } - if (updatePosition) { - cur.step(Some((res[s_2] as i32 / 2 - (centerBwd as i32 - 1)) as f32)); + if updatePosition { + cur.step(Some((res[s_2] as i32 / 2 - (centerBwd - 1)) as f32)); } res.into_iter().sum::() as i32 @@ -150,9 +150,9 @@ pub fn AverageEdgePixels( ) -> Option { let mut sum = Point::default(); - for i in 0..numOfEdges { + for _i in 0..numOfEdges { // for (int i = 0; i < numOfEdges; ++i) { - if (!cur.isInSelf()) { + if !cur.isInSelf() { return None; } cur.stepToEdge(Some(1), Some(range), None); @@ -200,7 +200,7 @@ pub fn CenterOfRing( let nth = nth.abs(); // log(center, 3); let mut cur = EdgeTracer::new(image, center, point(0.0, 1.0)); - if (!(cur.stepToEdge(Some(nth), Some(radius), Some(inner)) != 0)) { + if cur.stepToEdge(Some(nth), Some(radius), Some(inner)) == 0 { return None; } cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup @@ -220,18 +220,18 @@ pub fn CenterOfRing( n += 1; // find out if we come full circle around the center. 8 bits have to be set in the end. - neighbourMask |= (1 + neighbourMask |= 1 << (4.0 + Point::dot(Point::bresenhamDirection(cur.p() - center), point(1.0, 3.0))) - as u32); + as u32; - if (!cur.stepAlongEdge(edgeDir, None)) { + if !cur.stepAlongEdge(edgeDir, None) { return None; } // use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate - if (Point::maxAbsComponent(cur.p - center) > radius as f32 + if Point::maxAbsComponent(cur.p - center) > radius as f32 || center == cur.p - || n > 4 * 2 * range) + || n > 4 * 2 * range { return None; } @@ -241,7 +241,7 @@ pub fn CenterOfRing( } } //while (cur.p != start); - if (requireCircle && neighbourMask != 0b111101111) { + if requireCircle && neighbourMask != 0b111101111 { return None; } @@ -281,13 +281,13 @@ pub fn FinetuneConcentricPatternCenter( let mut res = CenterOfRings(image, center, range, finderPatternSize / 2); - if (res.is_none() || !image.get_point(res?)) { - res = CenterOfDoubleCross(image, (center), range, finderPatternSize / 2 + 1); + if res.is_none() || !image.get_point(res?) { + res = CenterOfDoubleCross(image, center, range, finderPatternSize / 2 + 1); } - if (res.is_none() || !image.get_point(res?)) { + if res.is_none() || !image.get_point(res?) { res = Some(center); } - if (res.is_none() || !image.get_point(res?)) { + if res.is_none() || !image.get_point(res?) { return None; } @@ -304,7 +304,7 @@ pub fn CollectRingPoints( let centerI = center.round(); let radius = range; let mut cur = EdgeTracer::new(image, centerI, point(0.0, 1.0)); - if (!(cur.stepToEdge(Some(edgeIndex), Some(radius), Some(backup)) != 0)) { + if cur.stepToEdge(Some(edgeIndex), Some(radius), Some(backup)) == 0 { return Vec::default(); } cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup @@ -320,21 +320,21 @@ pub fn CollectRingPoints( loop { // log(cur.p, 4); - points.push((cur.p().centered())); + points.push(cur.p().centered()); // find out if we come full circle around the center. 8 bits have to be set in the end. - neighbourMask |= (1 + neighbourMask |= 1 << (4.0 + Point::dot(Point::bresenhamDirection(cur.p - centerI), point(1.0, 3.0))) - as u32); + as u32; - if (!cur.stepAlongEdge(edgeDir, None)) { + if !cur.stepAlongEdge(edgeDir, None) { return Vec::default(); } // use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate - if (Point::maxAbsComponent(cur.p - centerI) > radius as f32 + if Point::maxAbsComponent(cur.p - centerI) > radius as f32 || centerI == cur.p - || (points).len() > 4 * 2 * range as usize) + || (points).len() > 4 * 2 * range as usize { return Vec::default(); } @@ -344,7 +344,7 @@ pub fn CollectRingPoints( } } //while (cur.p != start); - if (neighbourMask != 0b111101111) { + if neighbourMask != 0b111101111 { return Vec::default(); } @@ -389,7 +389,7 @@ pub fn FitQadrilateralToPoints(center: Point, points: &mut [Point]) -> Option Option Option { let mut points = CollectRingPoints(image, center, range, lineIndex, backup); - if (points.is_empty()) { + if points.is_empty() { return None; } let res = FitQadrilateralToPoints(center, &mut points)?; - if (!QuadrilateralIsPlausibleSquare(&res, (lineIndex - i32::from(backup)) as usize)) { + if !QuadrilateralIsPlausibleSquare(&res, (lineIndex - i32::from(backup)) as usize) { return None; } @@ -506,7 +506,7 @@ pub fn LocateConcentricPattern< cur.setDirection(d); // THIS COULD POSSIBLY BE WRONG, WE MIGHT MEAN TO CLONE cur EACH RUN? let spread = CheckSymmetricPattern::(&mut cur, pattern, range, true); - if (!(spread != 0)) { + if spread == 0 { return None; } UpdateMinMax(&mut minSpread, &mut maxSpread, spread); @@ -522,14 +522,14 @@ pub fn LocateConcentricPattern< range * 2, false, ); - if (!(spread != 0)) { + if spread == 0 { return None; } UpdateMinMax(&mut minSpread, &mut maxSpread, spread); } //#endif - if (maxSpread > 5 * minSpread) { + if maxSpread > 5 * minSpread { return None; } diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index cd0b7b3..406681a 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -7,7 +7,7 @@ pub struct FastEdgeToEdgeCounter { } impl FastEdgeToEdgeCounter { - pub fn new(cur: &T) -> Self { + pub fn new(_cur: &T) -> Self { todo!() // stride = cur.d.y * cur.img->width() + cur.d.x; // p = cur.img->row(cur.p.y).begin() + cur.p.x; @@ -17,7 +17,7 @@ impl FastEdgeToEdgeCounter { // stepsToBorder = std::min(maxStepsX, maxStepsY); } - pub fn stepToNextEdge(&self, range: i32) -> i32 { + pub fn stepToNextEdge(&self, _range: i32) -> i32 { todo!() // int maxSteps = std::min(stepsToBorder, range); // int steps = 0; diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index 3b6d82c..9198d1b 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -343,7 +343,7 @@ impl FixedPattern Self { - FixedPattern { data: data.clone() } + FixedPattern { data: *data } } fn as_slice(&self) -> &[PatternType] { @@ -655,9 +655,7 @@ mod tests { fn basic_pattern_view() { let mut p_row = PatternRow::default(); GetPatternRow( - &vec![ - 0_u16, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, - ], + &[0_u16, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1], &mut p_row, ); From e49f873bc91daafe2c7a08215c8beceb49a24442 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 13 Mar 2023 12:35:58 -0500 Subject: [PATCH 09/72] incomplete port of detector --- Cargo.toml | 1 + src/common/bit_array.rs | 12 + src/common/bit_matrix.rs | 23 ++ src/common/cpp_essentials/bitmatrix_cursor.rs | 181 ---------- .../cpp_essentials/bitmatrix_cursor_trait.rs | 184 ++++++++++ .../cpp_essentials/concentric_finder.rs | 53 ++- src/common/cpp_essentials/edge_tracer.rs | 10 +- .../fast_edge_to_edge_counter.rs | 4 +- src/common/cpp_essentials/mod.rs | 2 + src/common/cpp_essentials/pattern.rs | 29 +- src/common/cpp_essentials/util.rs | 12 + .../zxing_cpp_detector/cpp_new_detector.rs | 2 +- .../detector/zxing_cpp_detector/mod.rs | 2 +- src/qrcode/cpp_port/detector.rs | 327 ++++++++++++++++++ src/qrcode/cpp_port/mod.rs | 2 +- 15 files changed, 632 insertions(+), 212 deletions(-) create mode 100644 src/common/cpp_essentials/bitmatrix_cursor_trait.rs create mode 100644 src/qrcode/cpp_port/detector.rs diff --git a/Cargo.toml b/Cargo.toml index f01a483..f9f97ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ svg = {version = "0.13", optional = true} resvg = {version = "0.28.0", optional = true, default-features=false} serde = { version = "1.0", features = ["derive", "rc"], optional = true } thiserror = "1.0.38" +multimap = "0.8.3" [dev-dependencies] java-properties = "1.4.1" diff --git a/src/common/bit_array.rs b/src/common/bit_array.rs index 225c74c..bb9e95b 100644 --- a/src/common/bit_array.rs +++ b/src/common/bit_array.rs @@ -401,3 +401,15 @@ impl Default for BitArray { Self::new() } } + +impl Into> for BitArray { + fn into(self) -> Vec { + let mut arr = vec![0; self.get_size()]; + for x in 0..self.get_size() { + if self.get(x) { + arr[x] = 1; + } + } + arr + } +} diff --git a/src/common/bit_matrix.rs b/src/common/bit_matrix.rs index f55ccfe..0868d7b 100644 --- a/src/common/bit_matrix.rs +++ b/src/common/bit_matrix.rs @@ -408,6 +408,21 @@ impl BitMatrix { rw } + /// This method returns a column of the bitmatrix. + /// + /// The current implementation may be very slow. + pub fn getCol(&self, x: u32) -> BitArray { + let mut cw = BitArray::with_size(self.height as usize); + + for y in 0..self.height { + if self.get(x, y) { + cw.set(y as usize) + } + } + + cw + } + /** * @param y row to set * @param row {@link BitArray} to copy from @@ -595,6 +610,10 @@ impl BitMatrix { * @return The width of the matrix */ pub fn getWidth(&self) -> u32 { + self.width() + } + + pub fn width(&self) -> u32 { self.width } @@ -602,6 +621,10 @@ impl BitMatrix { * @return The height of the matrix */ pub fn getHeight(&self) -> u32 { + self.height() + } + + pub fn height(&self) -> u32 { self.height } diff --git a/src/common/cpp_essentials/bitmatrix_cursor.rs b/src/common/cpp_essentials/bitmatrix_cursor.rs index 2f059a0..8b13789 100644 --- a/src/common/cpp_essentials/bitmatrix_cursor.rs +++ b/src/common/cpp_essentials/bitmatrix_cursor.rs @@ -1,182 +1 @@ -use crate::Point; -use super::{util::opposite, Direction, Value}; - -/** - * @brief The BitMatrixCursor represents a current position inside an image and current direction it can advance towards. - * - * The current position and direction is a PointT. So depending on the type it can be used to traverse the image - * in a Bresenham style (PointF) or in a discrete way (step only horizontal/vertical/diagonal (PointI)). - */ -pub trait BitMatrixCursor { - // const BitMatrix* img; - - // POINT p; // current position - // POINT d; // current direction - - // BitMatrixCursor(const BitMatrix& image, POINT p, POINT d) : img(&image), p(p) { setDirection(d); } - - fn testAt(&self, p: Point) -> Value; //const - // { - // return img->isIn(p) ? Value{img->get(p)} : Value{}; - // } - - fn blackAt(&self, pos: Point) -> bool { - self.testAt(pos).isBlack() - } - fn whiteAt(&self, pos: Point) -> bool { - self.testAt(pos).isWhite() - } - - fn isIn(&self, p: Point) -> bool; // { return img->isIn(p); } - fn isInSelf(&self) -> bool; // { return self.isIn(p); } - fn isBlack(&self) -> bool; // { return blackAt(p); } - fn isWhite(&self) -> bool; // { return whiteAt(p); } - - fn front(&self) -> &Point; //{ return d; } - fn back(&self) -> Point; // { return {-d.x, -d.y}; } - fn left(&self) -> Point; //{ return {d.y, -d.x}; } - fn right(&self) -> Point; //{ return {-d.y, d.x}; } - fn direction(&self, dir: Direction) -> Point { - self.right() * Into::::into(dir) - } - - fn turnBack(&mut self); // noexcept { d = back(); } - fn turnLeft(&mut self); //noexcept { d = left(); } - fn turnRight(&mut self); //noexcept { d = right(); } - fn turn(&mut self, dir: Direction); //noexcept { d = direction(dir); } - - fn edgeAt_point(&self, d: Point) -> Value; - // { - // Value v = testAt(p); - // return testAt(p + d) != v ? v : Value(); - // } - - fn edgeAtFront(&self) -> Value { - return self.edgeAt_point(*self.front()); - } - fn edgeAtBack(&self) -> Value { - self.edgeAt_point(self.back()) - } - fn edgeAtLeft(&self) -> Value { - self.edgeAt_point(self.left()) - } - fn edgeAtRight(&self) -> Value { - self.edgeAt_point(self.right()) - } - fn edgeAt_direction(&self, dir: Direction) -> Value { - self.edgeAt_point(self.direction(dir)) - } - - fn setDirection(&mut self, dir: Point); // { d = bresenhamDirection(dir); } - // fn setDirection(&self, dir: Point);// { d = dir; } - - fn step(&mut self, s: Option) -> bool; // DEF to 1 - // { - // p += s * d; - // return isIn(p); - // } - - fn movedBy(self, d: Point) -> Self; - fn turnedBack(&self) -> Self; // { return {*img, p, back()}; } - // { - // auto res = *this; - // res.p += d; - // return res; - // } - - /** - * @brief stepToEdge advances cursor to one step behind the next (or n-th) edge. - * @param nth number of edges to pass - * @param range max number of steps to take - * @param backup whether or not to backup one step so we land in front of the edge - * @return number of steps taken or 0 if moved outside of range/image - */ - fn stepToEdge(&mut self, nth: Option, range: Option, backup: Option) -> i32; - // fn stepToEdge(&self, int nth = 1, int range = 0, bool backup = false) -> i32 - // { - // // TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt() - // int steps = 0; - // auto lv = testAt(p); - - // while (nth && (!range || steps < range) && lv.isValid()) { - // ++steps; - // auto v = testAt(p + steps * d); - // if (lv != v) { - // lv = v; - // --nth; - // } - // } - // if (backup) - // --steps; - // p += steps * d; - // return steps * (nth == 0); - // } - - fn stepAlongEdge(&mut self, dir: Direction, skipCorner: Option) -> bool -// fn stepAlongEdge(&self, dir:Direction, skipCorner:Option = false) -> bool - { - let skipCorner = if let Some(sc) = skipCorner { sc } else { false }; - - if !self.edgeAt_direction(dir).isValid() { - self.turn(dir); - } else if self.edgeAtFront().isValid() { - self.turn(opposite(dir)); - if self.edgeAtFront().isValid() { - self.turn(opposite(dir)); - if self.edgeAtFront().isValid() { - return false; - } - } - } - - let mut ret = self.step(None); - - if ret && skipCorner && !self.edgeAt_direction(dir).isValid() { - self.turn(dir); - ret = self.step(None); - } - - ret - } - - fn countEdges(&mut self, range: i32) -> i32 { - let mut res = 0; - let mut range = range; - - let mut steps; - - while { - steps = if range == 0 { - 0 - } else { - self.stepToEdge(Some(1), Some(range), None) - }; - steps > 0 - } { - range -= steps; - res += 1; - } - - res - } - - fn p(&self) -> Point; - - // template - // ARRAY readPattern(int range = 0) - // { - // ARRAY res; - // for (auto& i : res) - // i = stepToEdge(1, range); - // return res; - // } - - // template - // ARRAY readPatternFromBlack(int maxWhitePrefix, int range = 0) - // { - // if (maxWhitePrefix && isWhite() && !stepToEdge(1, maxWhitePrefix)) - // return {}; - // return readPattern(range); - // } -} diff --git a/src/common/cpp_essentials/bitmatrix_cursor_trait.rs b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs new file mode 100644 index 0000000..c19ac87 --- /dev/null +++ b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs @@ -0,0 +1,184 @@ +use crate::Point; + +use super::{util::opposite, Direction, Value}; + +/** + * @brief The BitMatrixCursor represents a current position inside an image and current direction it can advance towards. + * + * The current position and direction is a PointT. So depending on the type it can be used to traverse the image + * in a Bresenham style (PointF) or in a discrete way (step only horizontal/vertical/diagonal (PointI)). + */ +pub trait BitMatrixCursorTrait { + // const BitMatrix* img; + + // POINT p; // current position + // POINT d; // current direction + + // BitMatrixCursor(const BitMatrix& image, POINT p, POINT d) : img(&image), p(p) { setDirection(d); } + + fn testAt(&self, p: Point) -> Value; //const + // { + // return img->isIn(p) ? Value{img->get(p)} : Value{}; + // } + + fn blackAt(&self, pos: Point) -> bool { + self.testAt(pos).isBlack() + } + fn whiteAt(&self, pos: Point) -> bool { + self.testAt(pos).isWhite() + } + + fn isIn(&self, p: Point) -> bool; // { return img->isIn(p); } + fn isInSelf(&self) -> bool; // { return self.isIn(p); } + fn isBlack(&self) -> bool; // { return blackAt(p); } + fn isWhite(&self) -> bool; // { return whiteAt(p); } + + fn front(&self) -> &Point; //{ return d; } + fn back(&self) -> Point; // { return {-d.x, -d.y}; } + fn left(&self) -> Point; //{ return {d.y, -d.x}; } + fn right(&self) -> Point; //{ return {-d.y, d.x}; } + fn direction(&self, dir: Direction) -> Point { + self.right() * Into::::into(dir) + } + + fn turnBack(&mut self); // noexcept { d = back(); } + fn turnLeft(&mut self); //noexcept { d = left(); } + fn turnRight(&mut self); //noexcept { d = right(); } + fn turn(&mut self, dir: Direction); //noexcept { d = direction(dir); } + + fn edgeAt_point(&self, d: Point) -> Value; + // { + // Value v = testAt(p); + // return testAt(p + d) != v ? v : Value(); + // } + + fn edgeAtFront(&self) -> Value { + return self.edgeAt_point(*self.front()); + } + fn edgeAtBack(&self) -> Value { + self.edgeAt_point(self.back()) + } + fn edgeAtLeft(&self) -> Value { + self.edgeAt_point(self.left()) + } + fn edgeAtRight(&self) -> Value { + self.edgeAt_point(self.right()) + } + fn edgeAt_direction(&self, dir: Direction) -> Value { + self.edgeAt_point(self.direction(dir)) + } + + fn setDirection(&mut self, dir: Point); // { d = bresenhamDirection(dir); } + // fn setDirection(&self, dir: Point);// { d = dir; } + + fn step(&mut self, s: Option) -> bool; // DEF to 1 + // { + // p += s * d; + // return isIn(p); + // } + + fn movedBy(self, d: Point) -> Self; + fn turnedBack(&self) -> Self; // { return {*img, p, back()}; } + // { + // auto res = *this; + // res.p += d; + // return res; + // } + + /** + * @brief stepToEdge advances cursor to one step behind the next (or n-th) edge. + * @param nth number of edges to pass + * @param range max number of steps to take + * @param backup whether or not to backup one step so we land in front of the edge + * @return number of steps taken or 0 if moved outside of range/image + */ + fn stepToEdge(&mut self, nth: Option, range: Option, backup: Option) -> i32; + // fn stepToEdge(&self, int nth = 1, int range = 0, bool backup = false) -> i32 + // { + // // TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt() + // int steps = 0; + // auto lv = testAt(p); + + // while (nth && (!range || steps < range) && lv.isValid()) { + // ++steps; + // auto v = testAt(p + steps * d); + // if (lv != v) { + // lv = v; + // --nth; + // } + // } + // if (backup) + // --steps; + // p += steps * d; + // return steps * (nth == 0); + // } + + fn stepAlongEdge(&mut self, dir: Direction, skipCorner: Option) -> bool +// fn stepAlongEdge(&self, dir:Direction, skipCorner:Option = false) -> bool + { + let skipCorner = if let Some(sc) = skipCorner { sc } else { false }; + + if !self.edgeAt_direction(dir).isValid() { + self.turn(dir); + } else if self.edgeAtFront().isValid() { + self.turn(opposite(dir)); + if self.edgeAtFront().isValid() { + self.turn(opposite(dir)); + if self.edgeAtFront().isValid() { + return false; + } + } + } + + let mut ret = self.step(None); + + if ret && skipCorner && !self.edgeAt_direction(dir).isValid() { + self.turn(dir); + ret = self.step(None); + } + + ret + } + + fn countEdges(&mut self, range: i32) -> i32 { + let mut res = 0; + let mut range = range; + + let mut steps; + + while { + steps = if range == 0 { + 0 + } else { + self.stepToEdge(Some(1), Some(range), None) + }; + steps > 0 + } { + range -= steps; + res += 1; + } + + res + } + + fn p(&self) -> Point; + + fn d(&self) -> Point; + + // template + // ARRAY readPattern(int range = 0) + // { + // ARRAY res; + // for (auto& i : res) + // i = stepToEdge(1, range); + // return res; + // } + + // template + // ARRAY readPatternFromBlack(int maxWhitePrefix, int range = 0) + // { + // if (maxWhitePrefix && isWhite() && !stepToEdge(1, maxWhitePrefix)) + // return {}; + // return readPattern(range); + // } +} diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index 7758738..d393414 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -9,8 +9,8 @@ use crate::{ }; use super::{ - BitMatrixCursor, EdgeTracer, FastEdgeToEdgeCounter, Pattern, RegressionLine, - RegressionLineTrait, + BitMatrixCursorTrait, EdgeTracer, FastEdgeToEdgeCounter, Pattern, RegressionLine, + RegressionLineTrait, UpdateMinMax, UpdateMinMaxFloat, }; pub fn CenterFromEnd + std::iter::Sum + Copy>( @@ -41,7 +41,7 @@ pub fn CenterFromEnd + std::iter::Sum + Copy>( } } -pub fn ReadSymmetricPattern( +pub fn ReadSymmetricPattern( cur: &mut Cursor, range: i32, ) -> Option> { @@ -82,7 +82,7 @@ pub fn CheckSymmetricPattern< const RELAXED_THRESHOLD: bool, const LEN: usize, const SUM: usize, - T: BitMatrixCursor, + T: BitMatrixCursorTrait, >( cur: &mut T, pattern: &Pattern, @@ -143,7 +143,7 @@ pub fn CheckSymmetricPattern< res.into_iter().sum::() as i32 } -pub fn AverageEdgePixels( +pub fn AverageEdgePixels( cur: &mut T, range: i32, numOfEdges: u32, @@ -481,17 +481,42 @@ pub fn FindConcentricPatternCorners( Some(res) } -#[derive(Default)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)] pub struct ConcentricPattern { - p: Point, - size: i32, + pub p: Point, + pub size: i32, +} + +impl std::ops::Sub for ConcentricPattern { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + let new_p = self.p - rhs.p; + Self { + p: new_p, + size: self.size, + } + } +} + +impl ConcentricPattern { + pub fn dot(self, other: ConcentricPattern) -> f32 { + Point::dot(self.p, other.p) + } + + pub fn cross(self, other: ConcentricPattern) -> f32 { + Point::cross(self.p, other.p) + } + + pub fn distance(self, other: ConcentricPattern) -> f32 { + Point::distance(self.p, other.p) + } } pub fn LocateConcentricPattern< const RELAXED_THRESHOLD: bool, const LEN: usize, const SUM: usize, - T: BitMatrixCursor, >( image: &BitMatrix, pattern: &Pattern, @@ -540,13 +565,3 @@ pub fn LocateConcentricPattern< size: (maxSpread + minSpread) / 2, }) } - -fn UpdateMinMax(min: &mut T, max: &mut T, val: T) { - *min = std::cmp::min(*min, val); - *max = std::cmp::max(*max, val); -} - -fn UpdateMinMaxFloat(min: &mut f64, max: &mut f64, val: f64) { - *min = f64::min(*min, val); - *max = f64::max(*max, val); -} diff --git a/src/common/cpp_essentials/edge_tracer.rs b/src/common/cpp_essentials/edge_tracer.rs index b91b09f..e22145d 100644 --- a/src/common/cpp_essentials/edge_tracer.rs +++ b/src/common/cpp_essentials/edge_tracer.rs @@ -6,7 +6,7 @@ use crate::{ Exceptions, Point, }; -use super::{BitMatrixCursor, Direction, RegressionLineTrait, StepResult, Value}; +use super::{BitMatrixCursorTrait, Direction, RegressionLineTrait, StepResult, Value}; #[derive(Clone)] pub struct EdgeTracer<'a> { @@ -34,7 +34,7 @@ pub struct EdgeTracer<'a> { // } // } -impl BitMatrixCursor for EdgeTracer<'_> { +impl BitMatrixCursorTrait for EdgeTracer<'_> { fn testAt(&self, p: Point) -> Value { if self.img.isIn(p, 0) { Value::from(self.img.get_point(p)) @@ -119,7 +119,7 @@ impl BitMatrixCursor for EdgeTracer<'_> { self.isIn(self.p) } - fn movedBy(self, d: Point) -> Self { + fn movedBy(self, d: Point) -> Self { let mut res = self; res.p += d; @@ -166,6 +166,10 @@ impl BitMatrixCursor for EdgeTracer<'_> { fn p(&self) -> Point { self.p } + + fn d(&self) -> Point { + self.d + } } impl<'a> EdgeTracer<'_> { diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index 406681a..c1a5fcb 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -1,4 +1,4 @@ -use super::BitMatrixCursor; +use super::BitMatrixCursorTrait; pub struct FastEdgeToEdgeCounter { // const uint8_t* p = nullptr; @@ -7,7 +7,7 @@ pub struct FastEdgeToEdgeCounter { } impl FastEdgeToEdgeCounter { - pub fn new(_cur: &T) -> Self { + pub fn new(_cur: &T) -> Self { todo!() // stride = cur.d.y * cur.img->width() + cur.d.x; // p = cur.img->row(cur.p.y).begin() + cur.p.x; diff --git a/src/common/cpp_essentials/mod.rs b/src/common/cpp_essentials/mod.rs index 725388d..b1823bd 100644 --- a/src/common/cpp_essentials/mod.rs +++ b/src/common/cpp_essentials/mod.rs @@ -1,4 +1,5 @@ pub mod bitmatrix_cursor; +pub mod bitmatrix_cursor_trait; pub mod concentric_finder; pub mod direction; pub mod dm_regression_line; @@ -12,6 +13,7 @@ pub mod util; pub mod value; pub use bitmatrix_cursor::*; +pub use bitmatrix_cursor_trait::*; pub use concentric_finder::*; pub use direction::*; pub use dm_regression_line::*; diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index 9198d1b..b24915d 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -3,7 +3,10 @@ */ // SPDX-License-Identifier: Apache-2.0 -use crate::{common::Result, Exceptions}; +use crate::{ + common::{BitMatrix, Result}, + Exceptions, +}; pub type PatternType = u16; pub type Pattern = [PatternType; N]; @@ -337,8 +340,16 @@ pub struct FixedPattern Into> + for FixedPattern +{ + fn into(self) -> Pattern { + self.data + } +} + impl FixedPattern { - pub fn new(data: [PatternType; N]) -> Self { + pub const fn new(data: [PatternType; N]) -> Self { FixedPattern { data } } @@ -546,7 +557,15 @@ impl> From for Color { } } -fn GetPatternRow + Copy + Default + From>( +pub fn GetPatternRowTP(matrix: &BitMatrix, r: u32, pr: &mut PatternRow, transpose: bool) { + if (transpose) { + GetPatternRow(&Into::>::into(matrix.getCol(r)), pr) + } else { + GetPatternRow(&Into::>::into(matrix.getRow(r)), pr) + } +} + +pub fn GetPatternRow + Copy + Default + From>( b_row: &[T], p_row: &mut PatternRow, ) { @@ -655,7 +674,9 @@ mod tests { fn basic_pattern_view() { let mut p_row = PatternRow::default(); GetPatternRow( - &[0_u16, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1], + &[ + 0_u16, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + ], &mut p_row, ); diff --git a/src/common/cpp_essentials/util.rs b/src/common/cpp_essentials/util.rs index 6b580a1..d98b866 100644 --- a/src/common/cpp_essentials/util.rs +++ b/src/common/cpp_essentials/util.rs @@ -26,3 +26,15 @@ pub fn opposite(dir: Direction) -> Direction { Direction::Left } } + +#[inline(always)] +pub fn UpdateMinMax(min: &mut T, max: &mut T, val: T) { + *min = std::cmp::min(*min, val); + *max = std::cmp::max(*max, val); +} + +#[inline(always)] +pub fn UpdateMinMaxFloat(min: &mut f64, max: &mut f64, val: f64) { + *min = f64::min(*min, val); + *max = f64::max(*max, val); +} diff --git a/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs b/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs index 2b98e76..5d74e31 100644 --- a/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs +++ b/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs @@ -19,7 +19,7 @@ use crate::{ Quadrilateral, Result, }, datamatrix::detector::{ - zxing_cpp_detector::{util::intersect, BitMatrixCursor}, + zxing_cpp_detector::{util::intersect, BitMatrixCursorTrait}, DatamatrixDetectorResult, }, point, diff --git a/src/datamatrix/detector/zxing_cpp_detector/mod.rs b/src/datamatrix/detector/zxing_cpp_detector/mod.rs index f28f043..fa222b0 100644 --- a/src/datamatrix/detector/zxing_cpp_detector/mod.rs +++ b/src/datamatrix/detector/zxing_cpp_detector/mod.rs @@ -1,6 +1,6 @@ mod cpp_new_detector; -pub(self) use crate::common::cpp_essentials::bitmatrix_cursor::*; +pub(self) use crate::common::cpp_essentials::bitmatrix_cursor_trait::*; pub(self) use crate::common::cpp_essentials::direction::*; pub(self) use crate::common::cpp_essentials::dm_regression_line::*; pub(self) use crate::common::cpp_essentials::edge_tracer::*; diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs new file mode 100644 index 0000000..b1d5521 --- /dev/null +++ b/src/qrcode/cpp_port/detector.rs @@ -0,0 +1,327 @@ +use multimap::MultiMap; + +use crate::{ + common::{ + cpp_essentials::{ + BitMatrixCursorTrait, ConcentricPattern, Direction, EdgeTracer, FindLeftGuard, + FixedPattern, GetPatternRow, GetPatternRowTP, IsPattern, LocateConcentricPattern, + PatternRow, PatternType, PatternView, ReadSymmetricPattern, RegressionLine, + RegressionLineTrait, + }, + BitMatrix, + }, + point, Point, +}; + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +pub struct FinderPatternSet { + bl: ConcentricPattern, + tl: ConcentricPattern, + tr: ConcentricPattern, +} + +pub type FinderPatterns = Vec; +pub type FinderPatternSets = Vec; + +const PATTERN: FixedPattern<5, 7, false> = FixedPattern::new([1, 1, 3, 1, 1]); + +pub fn FindFinderPatterns(image: &BitMatrix, tryHarder: bool) -> FinderPatterns { + const MIN_SKIP: u32 = 3; // 1 pixel/module times 3 modules/center + const MAX_MODULES_FAST: u32 = 20 * 4 + 17; // support up to version 20 for mobile clients + + // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the + // image, and then account for the center being 3 modules in size. This gives the smallest + // number of pixels the center could be, so skip this often. When trying harder, look for all + // QR versions regardless of how dense they are. + let height = image.height(); + let mut skip = (3 * height) / (4 * MAX_MODULES_FAST); + if (skip < MIN_SKIP || tryHarder) { + skip = MIN_SKIP; + } + + let mut res: Vec = Vec::new(); + let mut y = skip - 1; + + while y < height { + // for (int y = skip - 1; y < height; y += skip) { + let mut row = PatternRow::default(); + GetPatternRowTP(image, y, &mut row, false); + let mut next: PatternView = PatternView::new(&row); + + while { + let next = FindLeftGuard(&next, 0, &PATTERN, 0.5).unwrap(); + next.isValid() + } { + let p = point( + next.pixelsInFront() as f32 + + next[0] as f32 + + next[1] as f32 + + next[2] as f32 / 2.0, + y as f32 + 0.5, + ); + + // make sure p is not 'inside' an already found pattern area + if res + .iter() + .find(|old| Point::distance(p, old.p) < (old.size as f32) / 2.0) + .is_none() + { + // if (FindIf(res, [p](const auto& old) { return distance(p, old) < old.size / 2; }) == res.end()) { + let pattern = LocateConcentricPattern::( + image, + &PATTERN.into(), + p, + next.sum::() as i32 * 3, + ); // 3 for very skewed samples + // Reduce(next) * 3); // 3 for very skewed samples + if (pattern.is_some()) { + // log(*pattern, 3); + assert!(image.get_point(pattern.as_ref().unwrap().p)); + res.push(pattern.unwrap()); + } + } + + next.skipPair(); + next.skipPair(); + next.extend(); + } + + y += skip; + } + + res +} + +/** + * @brief GenerateFinderPatternSets + * @param patterns list of ConcentricPattern objects, i.e. found finder pattern squares + * @return list of plausible finder pattern sets, sorted by decreasing plausibility + */ +pub fn GenerateFinderPatternSets(patterns: &mut FinderPatterns) -> FinderPatternSets { + patterns.sort_by_key(|p| p.size); + // std::sort(patterns.begin(), patterns.end(), [](const auto& a, const auto& b) { return a.size < b.size; }); + + let mut sets: MultiMap = MultiMap::new(); + let squaredDistance = |a: ConcentricPattern, b: ConcentricPattern| { + // The scaling of the distance by the b/a size ratio is a very coarse compensation for the shortening effect of + // the camera projection on slanted symbols. The fact that the size of the finder pattern is proportional to the + // distance from the camera is used here. This approximation only works if a < b < 2*a (see below). + // Test image: fix-finderpattern-order.jpg + ConcentricPattern::dot((a - b), (a - b)) as f64 + * (((b).size as f64) / ((a).size as f64)).powi(2) //std::pow(double(b.size) / a.size, 2) + }; + + let cosUpper: f64 = (45.0_f64 / 180.0 * 3.1415).cos(); // TODO: use c++20 std::numbers::pi_v + let cosLower: f64 = (135.0_f64 / 180.0 * 3.1415).cos(); + + let nbPatterns = (patterns).len(); + for i in 0..(nbPatterns - 2) { + // for (int i = 0; i < nbPatterns - 2; i++) { + for j in (i + 1)..(nbPatterns - 1) { + // for (int j = i + 1; j < nbPatterns - 1; j++) { + for k in (j + 1)..(nbPatterns - 0) { + // for (int k = j + 1; k < nbPatterns - 0; k++) { + let mut a = &patterns[i]; + let mut b = &patterns[j]; + let mut c = &patterns[k]; + // if the pattern sizes are too different to be part of the same symbol, skip this + // and the rest of the innermost loop (sorted list) + if (c.size > a.size * 2) { + break; + } + + // Orders the three points in an order [A,B,C] such that AB is less than AC + // and BC is less than AC, and the angle between BC and BA is less than 180 degrees. + + let mut distAB2 = squaredDistance(*a, *b); + let mut distBC2 = squaredDistance(*b, *c); + let mut distAC2 = squaredDistance(*a, *c); + + if (distBC2 >= distAB2 && distBC2 >= distAC2) { + std::mem::swap(&mut a, &mut b); + std::mem::swap(&mut distBC2, &mut distAC2); + } else if (distAB2 >= distAC2 && distAB2 >= distBC2) { + std::mem::swap(&mut b, &mut c); + std::mem::swap(&mut distAB2, &mut distAC2); + } + + let distAB = (distAB2.sqrt()); + let distBC = (distBC2).sqrt(); + + // Make sure distAB and distBC don't differ more than reasonable + // TODO: make sure the constant 2 is not to conservative for reasonably tilted symbols + if (distAB > 2.0 * distBC || distBC > 2.0 * distAB) { + continue; + } + + // Estimate the module count and ignore this set if it can not result in a valid decoding + let moduleCount = (distAB + distBC) + / (2.0 * (a.size + b.size + c.size) as f64 / (3.0 * 7.0)) + + 7.0; + if (moduleCount < 21.0 * 0.9 || moduleCount > 177.0 * 1.5) + // moduleCount may be overestimated, see above + { + continue; + } + + // Make sure the angle between AB and BC does not deviate from 90° by more than 45° + let cosAB_BC = (distAB2 + distBC2 - distAC2) / (2.0 * distAB * distBC); + if ((cosAB_BC.is_nan()) || cosAB_BC > cosUpper || cosAB_BC < cosLower) { + continue; + } + + // a^2 + b^2 = c^2 (Pythagorean theorem), and a = b (isosceles triangle). + // Since any right triangle satisfies the formula c^2 - b^2 - a^2 = 0, + // we need to check both two equal sides separately. + // The value of |c^2 - 2 * b^2| + |c^2 - 2 * a^2| increases as dissimilarity + // from isosceles right triangle. + let d: f64 = ((distAC2 - 2.0 * distAB2).abs() + (distAC2 - 2.0 * distBC2).abs()); + + // Use cross product to figure out whether A and C are correct or flipped. + // This asks whether BC x BA has a positive z component, which is the arrangement + // we want for A, B, C. If it's negative then swap A and C. + if (ConcentricPattern::cross(*c - *b, *a - *b) < 0.0) { + std::mem::swap(&mut a, &mut c); + } + + // arbitrarily limit the number of potential sets + // (this has performance implications while limiting the maximal number of detected symbols) + sets.insert( + d.to_string(), + FinderPatternSet { + bl: *a, + tl: *b, + tr: *c, + }, + ); + // const setSizeLimit : usize = 256; + // if (sets.len() < setSizeLimit || sets.crbegin().first > d) { + // sets.emplace(d, FinderPatternSet{a, b, c}); + // if (sets.len() > setSizeLimit) + // {sets.erase(std::prev(sets.end()));} + // } + } + } + } + + // convert from multimap to vector + let mut res: FinderPatternSets = Vec::with_capacity(sets.len()); + + for (k, v) in sets { + // for (auto& [d, s] : sets) + res.extend(v); + } + + res +} + +pub fn EstimateModuleSize(image: &BitMatrix, a: ConcentricPattern, b: ConcentricPattern) -> f64 { + let mut cur = EdgeTracer::new(image, a.p, b.p - a.p); + assert!(cur.isBlack()); + + let pattern = ReadSymmetricPattern::<5, _>(&mut cur, a.size * 2); + + if pattern.is_none() { + return -1.0; + } + + let pattern = pattern.unwrap(); + + if (!(IsPattern( + &PatternView::new(&PatternRow::new(pattern.to_vec())), + &PATTERN, + None, + 0.0, + 0.0, + Some(true), + ) != 0.0)) + { + return -1.0; + } + + (2 * pattern.iter().sum::() - pattern[0] - pattern[4]) as f64 / 12.0 + * cur.d().length() as f64 + // (2 * Reduce(*pattern) - (*pattern)[0] - (*pattern)[4]) / 12.0 * length(cur.d) +} + +pub struct DimensionEstimate { + dim: i32, + ms: f64, + err: i32, +} + +impl Default for DimensionEstimate { + fn default() -> Self { + Self { + dim: 0, + ms: 0.0, + err: 4, + } + } +} + +pub fn EstimateDimension( + image: &BitMatrix, + a: ConcentricPattern, + b: ConcentricPattern, +) -> DimensionEstimate { + let ms_a = EstimateModuleSize(image, a, b); + let ms_b = EstimateModuleSize(image, b, a); + + if (ms_a < 0.0 || ms_b < 0.0) { + return DimensionEstimate::default(); + } + + let moduleSize = (ms_a + ms_b) / 2.0; + + let dimension = ((ConcentricPattern::distance(a, b) as f64 / moduleSize).round() as i32 + 7); + let error = 1 - (dimension % 4); + + DimensionEstimate { + dim: dimension + error, + ms: moduleSize, + err: (error).abs(), + } +} + +pub fn TraceLine(image: &BitMatrix, p: Point, d: Point, edge: i32) -> impl RegressionLineTrait { + let mut cur = EdgeTracer::new(image, p, d - p); + let mut line = RegressionLine::default(); + line.setDirectionInward(cur.back()); + + // collect points inside the black line -> backup on 3rd edge + cur.stepToEdge(Some(edge), Some(0), Some(edge == 3)); + if (edge == 3) { + cur.turnBack(); + } + + let mut curI = EdgeTracer::new(image, (cur.p), (Point::mainDirection(cur.d()))); + // make sure curI positioned such that the white->black edge is directly behind + // Test image: fix-traceline.jpg + while (!bool::from(curI.edgeAtBack())) { + if (curI.edgeAtLeft().into()) { + curI.turnRight(); + } else if (curI.edgeAtRight().into()) { + curI.turnLeft(); + } else { + curI.step(Some(-1.0)); + } + } + + for dir in [Direction::Left, Direction::Right] { + // for (auto dir : {Direction::LEFT, Direction::RIGHT}) { + let mut c = EdgeTracer::new(image, curI.p, curI.direction(dir)); + let stepCount = (Point::maxAbsComponent(cur.p - p)) as i32; + loop { + line.add(Point::centered(c.p)); + + if !(--stepCount > 0 && c.stepAlongEdge(dir, Some(true))) { + break; + } + } //while (--stepCount > 0 && c.stepAlongEdge(dir, true)); + } + + line.evaluate_max_distance(Some(1.0), Some(true)); + + line +} diff --git a/src/qrcode/cpp_port/mod.rs b/src/qrcode/cpp_port/mod.rs index 9ac85ff..7b4f317 100644 --- a/src/qrcode/cpp_port/mod.rs +++ b/src/qrcode/cpp_port/mod.rs @@ -1,2 +1,2 @@ pub mod decoder; -// pub mod detector; +pub mod detector; From 07b934283bfac5bb06ad37b4e6fdce5c18fa9b49 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 16 Mar 2023 14:47:37 -0500 Subject: [PATCH 10/72] incomplete port of detector --- src/common/quad.rs | 4 +- src/qrcode/cpp_port/detector.rs | 83 ++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/common/quad.rs b/src/common/quad.rs index a2b94a6..262d39d 100644 --- a/src/common/quad.rs +++ b/src/common/quad.rs @@ -51,8 +51,8 @@ impl Quadrilateral { impl Quadrilateral { #[allow(dead_code)] - pub fn rectangle(width: i32, height: i32, margin: Option) -> Quadrilateral { - let margin = margin.unwrap_or(0); + pub fn rectangle(width: i32, height: i32, margin: Option) -> Quadrilateral { + let margin = margin.unwrap_or(0.0); Quadrilateral([ Point { diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index b1d5521..b6fda5f 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -1,3 +1,4 @@ +use crate::common::{cpp_essentials::CenterOfRing, Result}; use multimap::MultiMap; use crate::{ @@ -8,7 +9,7 @@ use crate::{ PatternRow, PatternType, PatternView, ReadSymmetricPattern, RegressionLine, RegressionLineTrait, }, - BitMatrix, + BitMatrix, PerspectiveTransform, Quadrilateral, }, point, Point, }; @@ -325,3 +326,83 @@ pub fn TraceLine(image: &BitMatrix, p: Point, d: Point, edge: i32) -> impl Regre line } + +// estimate how tilted the symbol is (return value between 1 and 2, see also above) +pub fn EstimateTilt(fp: &FinderPatternSet) -> f64 { + let min = [fp.bl.size, fp.tl.size, fp.tr.size] + .iter() + .min() + .copied() + .unwrap_or(i32::MAX); + let max = [fp.bl.size, fp.tl.size, fp.tr.size] + .iter() + .max() + .copied() + .unwrap_or(i32::MIN); + + (max as f64) / (min as f64) +} + +pub fn Mod2Pix( + dimension: i32, + brOffset: Point, + pix: Quadrilateral, +) -> Result { + let mut quad = Quadrilateral::rectangle(dimension, dimension, Some(3.5)); + // let quad = Rectangle(dimension, dimension, 3.5); + quad[2] = quad[2] - brOffset; + + PerspectiveTransform::quadrilateralToQuadrilateral(quad, pix) + // return {quad, pix}; +} + +pub fn LocateAlignmentPattern( + image: &BitMatrix, + moduleSize: i32, + estimate: Point, +) -> Option { + // log(estimate, 2); + + for d in [ + point(0.0, 0.0), + point(0.0, -1.0), + point(0.0, 1.0), + point(-1.0, 0.0), + point(1.0, 0.0), + point(-1.0, -1.0), + point(1.0, -1.0), + point(1.0, 1.0), + point(-1.0, 1.0), + ] { + // for (auto d : {PointF{0, 0}, {0, -1}, {0, 1}, {-1, 0}, {1, 0}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1}, + // #if 1 + // }) { + // #else + // {0, -2}, {0, 2}, {-2, 0}, {2, 0}, {-1, -2}, {1, -2}, {-1, 2}, {1, 2}, {-2, -1}, {-2, 1}, {2, -1}, {2, 1}}) { + // #endif + let cor = CenterOfRing( + image, + estimate + moduleSize as f32 * 2.25 * d, + moduleSize * 3, + 1, + false, + ); + + // if we did not land on a black pixel the concentric pattern finder will fail + if (cor.is_none() || !image.get_point(cor.unwrap())) { + continue; + } + + if let Some(cor1) = CenterOfRing(image, cor.unwrap(), moduleSize, 1, true) { + if let Some(cor2) = CenterOfRing(image, cor.unwrap(), moduleSize * 3, -2, true) { + if (Point::distance(cor1, cor2) < moduleSize as f32 / 2.0) { + let res = (cor1 + cor2) / 2.0; + // log(res, 3); + return Some(res); + } + } + } + } + + None +} From 4c8db1f73f448f2205f6cd5a3b758316e91ab29b Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sat, 18 Mar 2023 15:48:47 -0500 Subject: [PATCH 11/72] in_progress port from cpp --- .../cpp_essentials/concentric_finder.rs | 18 + .../cpp_essentials/dm_regression_line.rs | 6 + src/common/cpp_essentials/matrix.rs | 106 ++++++ src/common/cpp_essentials/mod.rs | 2 + src/common/perspective_transform.rs | 1 + src/common/quad.rs | 20 +- src/qrcode/cpp_port/detector.rs | 339 +++++++++++++++++- src/qrcode/decoder/version.rs | 49 ++- src/rxing_result_point.rs | 8 + 9 files changed, 544 insertions(+), 5 deletions(-) create mode 100644 src/common/cpp_essentials/matrix.rs diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index d393414..9831bec 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -499,6 +499,24 @@ impl std::ops::Sub for ConcentricPattern { } } +impl std::ops::Add for ConcentricPattern { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + let new_p = self.p - rhs.p; + Self { + p: new_p, + size: self.size, + } + } +} + +impl From for ConcentricPattern { + fn from(value: Point) -> Self { + Self { p: value, size: 0 } + } +} + impl ConcentricPattern { pub fn dot(self, other: ConcentricPattern) -> f32 { Point::dot(self.p, other.p) diff --git a/src/common/cpp_essentials/dm_regression_line.rs b/src/common/cpp_essentials/dm_regression_line.rs index dfb7cf0..2c4dfd8 100644 --- a/src/common/cpp_essentials/dm_regression_line.rs +++ b/src/common/cpp_essentials/dm_regression_line.rs @@ -223,6 +223,12 @@ impl RegressionLineTrait for DMRegressionLine { } impl DMRegressionLine { + pub fn new(point_1: Point, point_2: Point) -> Self { + let mut new = Self::default(); + RegressionLineTrait::evaluate(&mut new, &[point_1, point_1]); + new + } + // template fn average(c: &[f64], f: T) -> f64 where diff --git a/src/common/cpp_essentials/matrix.rs b/src/common/cpp_essentials/matrix.rs new file mode 100644 index 0000000..bcdc084 --- /dev/null +++ b/src/common/cpp_essentials/matrix.rs @@ -0,0 +1,106 @@ +use crate::common::Result; +use crate::{Exceptions, Point}; + +#[derive(Default, Clone, PartialEq, Eq)] +pub struct Matrix { + width: usize, + height: usize, + data: Vec, +} + +impl Matrix { + pub fn with_data(width: usize, height: usize, data: Vec) -> Result> { + if (width != 0 && data.len() / width as usize != height as usize) { + return Err(Exceptions::illegal_argument_with( + "invalid size: width * height is too big", + )); + } + Ok(Self { + width, + height, + data, + }) + } + + pub fn new(width: usize, height: usize) -> Result> { + if (width != 0 && height != 0) { + return Err(Exceptions::illegal_argument_with( + "invalid size: width * height is too big", + )); + } + Ok(Self { + width, + height, + data: vec![T::default(); width * height], + }) + } + + pub fn height(&self) -> usize { + self.height + } + + pub fn width(&self) -> usize { + self.width + } + + pub fn size(&self) -> usize { + self.data.len() + } + + // value_t& operator()(int x, int y) + // { + // assert(x >= 0 && x < _width && y >= 0 && y < _height); + // return _data[y * _width + x]; + // } + + // const T& operator()(int x, int y) const + // { + // assert(x >= 0 && x < _width && y >= 0 && y < _height); + // return _data[y * _width + x]; + // } + + fn get_offset(x: usize, y: usize, width: usize) -> usize { + (y * width + x) as usize + } + + pub fn get(&self, x: usize, y: usize) -> Option { + if x >= 0 && x < self.width && y >= 0 && y < self.height { + None + } else { + Some(self.data[Self::get_offset(x, y, self.width)]) + } + } + + pub fn set(&mut self, x: usize, y: usize, value: T) -> T { + self.data[Self::get_offset(x, y, self.width)] = value; + self.get(x, y).unwrap() + } + + pub fn get_point(&self, p: Point) -> Option { + self.get(p.x as usize, p.y as usize) + } + + pub fn set_point(&mut self, p: Point, value: T) -> T { + self.set(p.x as usize, p.y as usize, value) + } + + pub fn data(&self) -> &[T] { + &self.data + } + + // const value_t* begin() const { + // return _data.data(); + // } + + // const value_t* end() const { + // return _data.data() + _width * _height; + // } + + pub fn clear_with(&mut self, value: T) { + self.data.fill(value) + } + + pub fn clear(&mut self) { + self.data.fill(T::default()) + } +} diff --git a/src/common/cpp_essentials/mod.rs b/src/common/cpp_essentials/mod.rs index b1823bd..4d85f66 100644 --- a/src/common/cpp_essentials/mod.rs +++ b/src/common/cpp_essentials/mod.rs @@ -5,6 +5,7 @@ pub mod direction; pub mod dm_regression_line; pub mod edge_tracer; pub mod fast_edge_to_edge_counter; +pub mod matrix; pub mod pattern; pub mod regression_line; pub mod regression_line_trait; @@ -19,6 +20,7 @@ pub use direction::*; pub use dm_regression_line::*; pub use edge_tracer::*; pub use fast_edge_to_edge_counter::*; +pub use matrix::*; pub use pattern::*; pub use regression_line::*; pub use regression_line_trait::*; diff --git a/src/common/perspective_transform.rs b/src/common/perspective_transform.rs index cad843d..5c291bd 100644 --- a/src/common/perspective_transform.rs +++ b/src/common/perspective_transform.rs @@ -29,6 +29,7 @@ use super::Quadrilateral; * * @author Sean Owen */ +#[derive(Debug, Copy, Clone, PartialEq)] pub struct PerspectiveTransform { a11: f32, a12: f32, diff --git a/src/common/quad.rs b/src/common/quad.rs index 262d39d..4673eb1 100644 --- a/src/common/quad.rs +++ b/src/common/quad.rs @@ -1,4 +1,6 @@ -use crate::Point; +use crate::{point, Point}; + +use super::PerspectiveTransform; #[derive(Clone, Copy, Debug)] pub struct Quadrilateral(pub [Point; 4]); @@ -74,6 +76,16 @@ impl Quadrilateral { ]) } + pub fn rectangle_from_xy(x0: f32, x1: f32, y0: f32, y1: f32, o: Option) -> Self { + let o = o.unwrap_or(0.5); + Quadrilateral::from([ + point(x0 + o, y0 + o), + point(x1 + o, y0 + o), + point(x1 + o, y1 + o), + point(x0 + o, y1 + o), + ]) + } + #[allow(dead_code)] pub fn centered_square(size: i32) -> Quadrilateral { Self::scale( @@ -256,3 +268,9 @@ impl std::ops::IndexMut for Quadrilateral { &mut self.0[index] } } + +impl From<[Point; 4]> for Quadrilateral { + fn from(value: [Point; 4]) -> Self { + Self(value) + } +} diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index b6fda5f..550e821 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -1,4 +1,15 @@ -use crate::common::{cpp_essentials::CenterOfRing, Result}; +use crate::{ + common::{ + cpp_essentials::{CenterOfRing, DMRegressionLine, FindConcentricPatternCorners, Matrix}, + DefaultGridSampler, GridSampler, Result, SamplerControl, + }, + dimension, point_g, point_i, + qrcode::{ + decoder::{Version, VersionRef}, + detector::QRCodeDetectorResult, + }, + Exceptions, +}; use multimap::MultiMap; use crate::{ @@ -406,3 +417,329 @@ pub fn LocateAlignmentPattern( None } + +pub fn ReadVersion( + image: &BitMatrix, + dimension: u32, + mod2Pix: PerspectiveTransform, +) -> Result { + let mut bits = [0; 2]; // + + for mirror in [false, true] { + // Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored) + let mut versionBits = 0; + for y in (0..5).rev() { + // for (int y = 5; y >= 0; --y) + for x in ((dimension - 11)..(dimension - 9)).rev() { + // for (int x = dimension - 9; x >= dimension - 11; --x) { + let mod_ = if mirror { point_i(y, x) } else { point_i(x, y) }; + let pix = mod2Pix.transform_point((mod_).centered()); + if (!image.is_in(pix)) { + versionBits = -1; + } else { + AppendBit(&mut versionBits, image.get_point(pix)); + } + // log(pix, 3); + } + bits[usize::from(mirror)] = versionBits; + } + } + + Version::DecodeVersionInformation(bits[0], bits[1]) +} + +fn AppendBit(val: &mut i32, bit: bool) { + *val <<= 1; + + *val |= i32::from(bit) +} + +pub fn SampleQR(image: &BitMatrix, fp: &FinderPatternSet) -> Result { + let top = EstimateDimension(image, fp.tl, fp.tr); + let left = EstimateDimension(image, fp.tl, fp.bl); + + if (!(top.dim != 0) && !(left.dim != 0)) { + return Err(Exceptions::NOT_FOUND); + } + + let best = if top.err == left.err { + (if top.dim > left.dim { top } else { left }) + } else { + (if top.err < left.err { top } else { left }) + }; + let mut dimension = best.dim; + let moduleSize = (best.ms + 1.0) as i32; + + let mut br = ConcentricPattern { + p: point(-1.0, -1.0), + size: 0, + }; + let mut brOffset = point_i(3, 3); + + // Everything except version 1 (21 modules) has an alignment pattern. Estimate the center of that by intersecting + // line extensions of the 1 module wide square around the finder patterns. This could also help with detecting + // slanted symbols of version 1. + + // generate 4 lines: outer and inner edge of the 1 module wide black line between the two outer and the inner + // (tl) finder pattern + let bl2 = TraceLine(image, fp.bl.p, fp.tl.p, 2); + let bl3 = TraceLine(image, fp.bl.p, fp.tl.p, 3); + let tr2 = TraceLine(image, fp.tr.p, fp.tl.p, 2); + let tr3 = TraceLine(image, fp.tr.p, fp.tl.p, 3); + + if (bl2.isValid() && tr2.isValid() && bl3.isValid() && tr3.isValid()) { + // intersect both outer and inner line pairs and take the center point between the two intersection points + let brInter = (DMRegressionLine::intersect(&bl2, &tr2).ok_or(Exceptions::NOT_FOUND)? + + DMRegressionLine::intersect(&bl3, &tr3).ok_or(Exceptions::NOT_FOUND)?) + / 2.0; + // log(brInter, 3); + + if (dimension > 21) { + if let Some(brCP) = LocateAlignmentPattern(image, moduleSize, brInter) { + br = brCP.into(); + } + } + + // if the symbol is tilted or the resolution of the RegressionLines is sufficient, use their intersection + // as the best estimate (see discussion in #199 and test image estimate-tilt.jpg ) + if (!image.is_in(br.p) + && (EstimateTilt(fp) > 1.1 + || (bl2.isHighRes() && bl3.isHighRes() && tr2.isHighRes() && tr3.isHighRes()))) + { + br = brInter.into(); + } + } + + // otherwise the simple estimation used by upstream is used as a best guess fallback + if (!image.is_in(br.p)) { + br = fp.tr - fp.tl + fp.bl; + brOffset = point_i(0, 0); + } + + // log(br, 3); + let mut mod2Pix = Mod2Pix( + dimension, + brOffset, + Quadrilateral::from([fp.tl.p, fp.tr.p, br.p, fp.bl.p]), + )?; + + if (dimension >= Version::DimensionOfVersion(7, false) as i32) { + let version = ReadVersion(image, dimension as u32, mod2Pix.clone()); + + // if the version bits are garbage -> discard the detection + if (!version.is_ok() + || (version.as_ref().unwrap().getDimensionForVersion() as i32 - dimension).abs() > 8) + { + /*return DetectorResult();*/ + return Err(Exceptions::NOT_FOUND); + } + if (version.as_ref().unwrap().getDimensionForVersion() as i32 != dimension) { + // printf("update dimension: %d -> %d\n", dimension, version.dimension()); + dimension = version.as_ref().unwrap().getDimensionForVersion() as i32; + mod2Pix = Mod2Pix( + dimension, + brOffset, + Quadrilateral::from([fp.tl.p, fp.tr.p, br.p, fp.bl.p]), + )?; + } + // #if 1 + let apM = version.as_ref().unwrap().getAlignmentPatternCenters(); // alignment pattern positions in modules + let mut apP = Matrix::new(apM.len(), apM.len())?; // found/guessed alignment pattern positions in pixels + // let apP = Matrix>(Size(apM), Size(apM)); // found/guessed alignment pattern positions in pixels + let N = (apM.len()) - 1; + + // project the alignment pattern at module coordinates x/y to pixel coordinate based on current mod2Pix + let projectM2P = /*[&mod2Pix, &apM]*/| x, y, mod2Pix: &PerspectiveTransform| { return mod2Pix.transform_point(Point::centered(point_i(apM[x], apM[y]))); }; + + let mut findInnerCornerOfConcentricPattern = /*[&image, &apP, &projectM2P]*/| x, y, fp:ConcentricPattern| { + let pc = apP.set(x, y, projectM2P(x, y, &mod2Pix)); + if let Some(fpQuad) = FindConcentricPatternCorners(image, fp.p, fp.size, 2) + // if (auto fpQuad = FindConcentricPatternCorners(image, fp, fp.size, 2)) + {for c in fpQuad .0 + {if (Point::distance(c, pc) < (fp.size as f32) / 2.0) + {apP.set(x, y, c);}}} + }; + + findInnerCornerOfConcentricPattern(0, 0, fp.tl); + findInnerCornerOfConcentricPattern(0, N, fp.bl); + findInnerCornerOfConcentricPattern(N, 0, fp.tr); + + let bestGuessAPP = |x, y, apP: &Matrix| { + if let Some(p) = apP.get(x, y) + // if (auto p = apP(x, y)) + { + return p; + } + return projectM2P(x, y, &mod2Pix); + }; + + for y in 0..=N { + // for (int y = 0; y <= N; ++y) + for x in 0..=N { + // for (int x = 0; x <= N; ++x) { + if (apP.get(x, y).is_some()) { + continue; + } + + let guessed = if x * y == 0 { + bestGuessAPP(x, y, &apP) + } else { + bestGuessAPP(x - 1, y, &apP) + bestGuessAPP(x, y - 1, &apP) + - bestGuessAPP(x - 1, y - 1, &apP) + }; + if let Some(found) = LocateAlignmentPattern(image, moduleSize, guessed) + // if (auto found = LocateAlignmentPattern(image, moduleSize, guessed)) + { + apP.set(x, y, found); + } + } + } + + // go over the whole set of alignment patters again and try to fill any remaining gap by using available neighbors as guides + for y in 0..=N { + // for (int y = 0; y <= N; ++y) { + for x in 0..=N { + // for (int x = 0; x <= N; ++x) { + if (apP.get(x, y).is_some()) { + continue; + } + + // find the two closest valid alignment pattern pixel positions both horizontally and vertically + let mut hori = Vec::new(); + let mut verti = Vec::new(); + let mut i = 2; + while i < 2 * N + 2 && hori.len() < 2 { + let xi = x as isize + i as isize / 2 * (if i % 2 != 0 { 1 } else { -1 }); + if (0 <= xi && xi <= N as isize && apP.get(xi as usize, y).is_some()) { + hori.push( + apP.get(xi as usize, y) + .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?, + ); + } + i += 1; + } + // for (int i = 2; i < 2 * N + 2 && Size(hori) < 2; ++i) { + // let xi = x + i / 2 * (i%2 ? 1 : -1); + // if (0 <= xi && xi <= N && apP(xi, y)) + // {hori.push_back(*apP(xi, y));} + // } + let mut i = 2; + while i < 2 * N + 2 && verti.len() < 2 { + let yi = y as isize + i as isize / 2 * (if i % 2 != 0 { 1 } else { -1 }); + if (0 <= yi && yi <= N as isize && apP.get(x, yi as usize).is_some()) { + verti.push( + apP.get(x, yi as usize) + .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?, + ); + } + i += 1; + } + // for (int i = 2; i < 2 * N + 2 && Size(verti) < 2; ++i) { + // let yi = y + i / 2 * (i%2 ? 1 : -1); + // if (0 <= yi && yi <= N && apP(x, yi)) + // {verti.push_back(*apP(x, yi));} + // } + + // if we found 2 each, intersect the two lines that are formed by connecting the point pairs + if ((hori.len()) == 2 && (verti.len()) == 2) { + let guessed = RegressionLine::intersect( + &DMRegressionLine::new(hori[0], hori[1]), + &DMRegressionLine::new(verti[0], verti[1]), + ) + .ok_or(Exceptions::ILLEGAL_STATE)?; + let found = LocateAlignmentPattern(image, moduleSize, guessed); + // search again near that intersection and if the search fails, use the intersection + // if (!found.is_some()) {printf("location guessed at %dx%d\n", x, y)}; + apP.set( + x, + y, + if found.is_some() { + found.unwrap() + } else { + guessed + }, + ); + } + } + } + + if let Some(c) = apP.get(N, N) + // if (auto c = apP.get(N, N)) + { + mod2Pix = Mod2Pix( + dimension, + point_i(3, 3), + Quadrilateral::from([fp.tl.p, fp.tr.p, c, fp.bl.p]), + )?; + } + + // go over the whole set of alignment patters again and fill any remaining gaps by a projection based on an updated mod2Pix + // projection. This works if the symbol is flat, wich is a reasonable fall-back assumption. + for y in 0..=N { + // for (int y = 0; y <= N; ++y) { + for x in 0..=N { + // for (int x = 0; x <= N; ++x) { + if (apP.get(x, y).is_some()) { + continue; + } + + // printf("locate failed at %dx%d\n", x, y); + apP.set(x, y, projectM2P(x, y, &mod2Pix)); + } + } + + // assemble a list of region-of-interests based on the found alignment pattern pixel positions + + let mut rois = Vec::new(); + for y in 0..N { + // for (int y = 0; y < N; ++y){ + for x in 0..N { + // for (int x = 0; x < N; ++x) { + let x0 = apM[x]; + let x1 = apM[x + 1]; + let y0 = apM[y]; + let y1 = apM[y + 1]; + rois.push(SamplerControl { + p0: point_i(x0 - u32::from(x == 0) * 6, x1 + u32::from(x == N - 1) * 7), + p1: point_i(y0 - u32::from(y == 0) * 6, y1 + u32::from(y == N - 1) * 7), + transform: PerspectiveTransform::quadrilateralToQuadrilateral( + Quadrilateral::rectangle_from_xy( + x0 as f32, x1 as f32, y0 as f32, y1 as f32, None, + ), + Quadrilateral::from([ + apP.get(x, y).unwrap(), + apP.get(x + 1, y).unwrap(), + apP.get(x + 1, y + 1).unwrap(), + apP.get(x, y + 1).unwrap(), + ]), + )?, + }); + } + } + let grid_sampler = DefaultGridSampler::default(); + let result = QRCodeDetectorResult::new( + grid_sampler.sample_grid(image, dimension as u32, dimension as u32, &rois)?, + Vec::default(), + ); + return Ok(result); + // grid_sampler.sample_grid(image, dimension, dimension, &rois); + // #endif + } + + let grid_sampler = DefaultGridSampler::default(); + let result = QRCodeDetectorResult::new( + grid_sampler.sample_grid( + image, + dimension as u32, + dimension as u32, + &[SamplerControl { + p0: point_i(0, dimension as u32), + p1: point_i(0, dimension as u32), + transform: mod2Pix, + }], + )?, + Vec::default(), + ); + Ok(result) + // return SampleGrid(image, dimension, dimension, mod2Pix); +} diff --git a/src/qrcode/decoder/version.rs b/src/qrcode/decoder/version.rs index 8ca060b..cb9828c 100755 --- a/src/qrcode/decoder/version.rs +++ b/src/qrcode/decoder/version.rs @@ -89,6 +89,18 @@ impl Version { 17 + 4 * self.versionNumber } + pub fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 { + Self::DimensionOffset(is_micro) + Self::DimensionStep(is_micro) * version + } + + pub fn DimensionOffset(is_micro: bool) -> u32 { + todo!() + } + + pub fn DimensionStep(is_micro: bool) -> u32 { + todo!() + } + pub fn getECBlocksForLevel(&self, ecLevel: ErrorCorrectionLevel) -> &ECBlocks { &self.ecBlocks[ecLevel.get_ordinal() as usize] } @@ -100,21 +112,21 @@ impl Version { * @return Version for a QR Code of that dimension * @throws FormatException if dimension is not 1 mod 4 */ - pub fn getProvisionalVersionForDimension(dimension: u32) -> Result<&'static Version> { + pub fn getProvisionalVersionForDimension(dimension: u32) -> Result { if dimension % 4 != 1 { return Err(Exceptions::format_with("dimension incorrect")); } Self::getVersionForNumber((dimension - 17) / 4) } - pub fn getVersionForNumber(versionNumber: u32) -> Result<&'static Version> { + pub fn getVersionForNumber(versionNumber: u32) -> Result { if !(1..=40).contains(&versionNumber) { return Err(Exceptions::illegal_argument_with("version out of spec")); } Ok(&VERSIONS[versionNumber as usize - 1]) } - pub fn decodeVersionInformation(versionBits: u32) -> Result<&'static Version> { + pub fn decodeVersionInformation(versionBits: u32) -> Result { let mut bestDifference = u32::MAX; let mut bestVersion = 0; for i in 0..VERSION_DECODE_INFO.len() as u32 { @@ -140,6 +152,37 @@ impl Version { Err(Exceptions::NOT_FOUND) } + pub fn DecodeVersionInformation(versionBitsA: i32, versionBitsB: i32) -> Result { + let mut bestDifference = u32::MAX; + let mut bestVersion = 0; + let mut i = 0; + for targetVersion in VERSION_DECODE_INFO { + // for (int targetVersion : VERSION_DECODE_INFO) { + // Do the version info bits match exactly? done. + if targetVersion == versionBitsA as u32 || targetVersion == versionBitsB as u32 { + return Self::getVersionForNumber(i + 7); + } + // Otherwise see if this is the closest to a real version info bit string + // we have seen so far + for bits in [versionBitsA, versionBitsB] { + // for (int bits : {versionBitsA, versionBitsB}) { + let bitsDifference = ((bits as u32) ^ targetVersion).count_ones(); //BitHacks::CountBitsSet(bits ^ targetVersion); + if bitsDifference < bestDifference { + bestVersion = i + 7; + bestDifference = bitsDifference; + } + } + i += 1; + } + // We can tolerate up to 3 bits of error since no two version info codewords will + // differ in less than 8 bits. + if bestDifference <= 3 { + return Self::getVersionForNumber(bestVersion); + } + // If we didn't find a close enough match, fail + return Err(Exceptions::ILLEGAL_STATE); + } + /** * See ISO 18004:2006 Annex E */ diff --git a/src/rxing_result_point.rs b/src/rxing_result_point.rs index da5e401..14a7db6 100644 --- a/src/rxing_result_point.rs +++ b/src/rxing_result_point.rs @@ -25,6 +25,14 @@ pub fn point(x: f32, y: f32) -> Point { Point::new(x, y) } +pub fn point_g>(x: T, y: T) -> Option { + Some(Point::new(x.try_into().ok()?, y.try_into().ok()?)) +} + +pub fn point_i(x: u32, y: u32) -> Point { + Point::new(x as f32, y as f32) +} + /** Currently necessary because the external OneDReader proc macro uses it. */ pub type RXingResultPoint = Point; From f3af7d72a9e3a88cecb8098a2eab0c02fd7c74f9 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sun, 19 Mar 2023 15:03:46 -0500 Subject: [PATCH 12/72] checking, midwork --- src/common/bit_matrix.rs | 27 +++ .../cpp_essentials/bitmatrix_cursor_trait.rs | 37 ++-- src/qrcode/cpp_port/detector.rs | 164 ++++++++++++++++++ src/qrcode/decoder/version.rs | 6 +- 4 files changed, 216 insertions(+), 18 deletions(-) diff --git a/src/common/bit_matrix.rs b/src/common/bit_matrix.rs index 0868d7b..975219b 100644 --- a/src/common/bit_matrix.rs +++ b/src/common/bit_matrix.rs @@ -559,6 +559,33 @@ impl BitMatrix { Some([left, top, right - left + 1, bottom - top + 1]) } + pub fn + findBoundingBox(&self, left:&mut u32, top:&mut u32, width:&mut u32, height:&mut u32, minSize:u32) -> bool + { + todo!() + // let right; + // let bottom; + // if (!self.getTopLeftOnBitWithPosition(left, top) || !self.getBottomRightOnBitWithPosition(right, bottom) || bottom - top + 1 < minSize) + // {return false;} + + // for (int y = top; y <= bottom; y++ ) { + // for (int x = 0; x < left; ++x) + // if (get(x, y)) { + // left = x; + // break; + // } + // for (int x = _width-1; x > right; x--) + // if (get(x, y)) { + // right = x; + // break; + // } + // } + + // width = right - left + 1; + // height = bottom - top + 1; + // return width >= minSize && height >= minSize; + } + /** * This is useful in detecting a corner of a 'pure' barcode. * diff --git a/src/common/cpp_essentials/bitmatrix_cursor_trait.rs b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs index c19ac87..133e19a 100644 --- a/src/common/cpp_essentials/bitmatrix_cursor_trait.rs +++ b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs @@ -165,20 +165,27 @@ pub trait BitMatrixCursorTrait { fn d(&self) -> Point; - // template - // ARRAY readPattern(int range = 0) - // { - // ARRAY res; - // for (auto& i : res) - // i = stepToEdge(1, range); - // return res; - // } + + fn readPattern(&self, range : Option) -> Option + { + // let range = range.unwrap_or(0); + // let mut res :T; + // for i in res.into_iter() { + // i = self.stepToEdge(1, range); + // } + // // for (auto& i : res) + // // i = stepToEdge(1, range); + // return res; + todo!() + } - // template - // ARRAY readPatternFromBlack(int maxWhitePrefix, int range = 0) - // { - // if (maxWhitePrefix && isWhite() && !stepToEdge(1, maxWhitePrefix)) - // return {}; - // return readPattern(range); - // } + + fn readPatternFromBlack(&self, maxWhitePrefix:i32, range: Option) -> Option + { + let range = range.unwrap_or(0); + if (maxWhitePrefix != 0 && self.isWhite() && !self.stepToEdge(Some(1), Some(maxWhitePrefix), None) > 0) + {return None;} + // return readPattern(range); + self.readPattern(Some(range)) + } } diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index 550e821..4f8997b 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -743,3 +743,167 @@ pub fn SampleQR(image: &BitMatrix, fp: &FinderPatternSet) -> Result Result +{ + type Pattern = Vec; + +// #ifdef PRINT_DEBUG +// SaveAsPBM(image, "weg.pbm"); +// #endif + + const MIN_MODULES: u32 = Version::DimensionOfVersion(1, false); + const MAX_MODULES:u32 = Version::DimensionOfVersion(40, false); + + let left; + let top; + let width; + let height; + + if (!image.findBoundingBox(left, top, width, height, MIN_MODULES) || std::abs(width - height) > 1) + {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 bl = point_i(*left, *bottom); + let diagonal : Pattern; + // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts + for [p,d] in [[tl, point_i(1,1)], [tr, point(-1.0,1.0)], [bl, point(1.0,-1.0)]] { + // for (auto [p, d] : {std::pair(tl, PointI{1, 1}), {tr, {-1, 1}}, {bl, {1, -1}}}) { + diagonal = EdgeTracer::new(image, p, d).readPatternFromBlack(1, width / 3 + 1).ok_or(Exceptions::NOT_FOUND)?; + // diagonal = BitMatrixCursorI(image, p, d).readPatternFromBlack(1, width / 3 + 1); + if (!IsPattern(diagonal, PATTERN) != 0.0) + {return Err(Exceptions::NOT_FOUND);} + } + + let fpWidth = Reduce(diagonal); + let dimension = + EstimateDimension(image, {tl + fpWidth / 2 * PointF(1, 1), fpWidth}, {tr + fpWidth / 2 * PointF(-1, 1), fpWidth}).dim; + + let moduleSize:f32 = float(width) / dimension; + if (dimension < MIN_MODULES || dimension > MAX_MODULES || + !image.isIn(PointF{left + moduleSize / 2 + (dimension - 1) * moduleSize, + top + moduleSize / 2 + (dimension - 1) * moduleSize})) + {return Err(Exceptions::NOT_FOUND);} + +// #ifdef PRINT_DEBUG +// LogMatrix log; +// LogMatrixWriter lmw(log, image, 5, "grid2.pnm"); +// for (int y = 0; y < dimension; y++) +// for (int x = 0; x < dimension; x++) +// log(PointF(left + (x + .5f) * moduleSize, top + (y + .5f) * moduleSize)); +// #endif + + // Now just read off the bits (this is a crop + subsample) + return {Deflate(image, dimension, dimension, top + moduleSize / 2, left + moduleSize / 2, moduleSize), + {{left, top}, {right, top}, {right, bottom}, {left, bottom}}}; +} + +pub fn DetectPureMQR( image:&BitMatrix) -> Result +{ + using Pattern = std::array; + + constexpr int MIN_MODULES = Version::DimensionOfVersion(1, true); + constexpr int MAX_MODULES = Version::DimensionOfVersion(4, true); + + int left, top, width, height; + if (!image.findBoundingBox(left, top, width, height, MIN_MODULES) || std::abs(width - height) > 1) + return {}; + int right = left + width - 1; + int bottom = top + height - 1; + + // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts + auto diagonal = BitMatrixCursorI(image, {left, top}, {1, 1}).readPatternFromBlack(1); + if (!IsPattern(diagonal, PATTERN)) + return {}; + + auto fpWidth = Reduce(diagonal); + float moduleSize = float(fpWidth) / 7; + int dimension = narrow_cast(std::lround(width / moduleSize)); + + if (dimension < MIN_MODULES || dimension > MAX_MODULES || + !image.isIn(PointF{left + moduleSize / 2 + (dimension - 1) * moduleSize, + top + moduleSize / 2 + (dimension - 1) * moduleSize})) + return {}; + +// #ifdef PRINT_DEBUG +// LogMatrix log; +// LogMatrixWriter lmw(log, image, 5, "grid2.pnm"); +// for (int y = 0; y < dimension; y++) +// for (int x = 0; x < dimension; x++) +// log(PointF(left + (x + .5f) * moduleSize, top + (y + .5f) * moduleSize)); +// #endif + + // Now just read off the bits (this is a crop + subsample) + return {Deflate(image, dimension, dimension, top + moduleSize / 2, left + moduleSize / 2, moduleSize), + {{left, top}, {right, top}, {right, bottom}, {left, bottom}}}; +} + +pub fn SampleMQR( image:&BitMatrix, fp:ConcentricPattern) -> Result +{ + auto fpQuad = FindConcentricPatternCorners(image, fp, fp.size, 2); + if (!fpQuad) + return {}; + + auto srcQuad = Rectangle(7, 7, 0.5); + +// #if defined(_MSVC_LANG) // TODO: see MSVC issue https://developercommunity.visualstudio.com/t/constexpr-object-is-unable-to-be-used-as/10035065 +// static +// #else +// constexpr +// #endif + const PointI FORMAT_INFO_COORDS[] = {{0, 8}, {1, 8}, {2, 8}, {3, 8}, {4, 8}, {5, 8}, {6, 8}, {7, 8}, {8, 8}, + {8, 7}, {8, 6}, {8, 5}, {8, 4}, {8, 3}, {8, 2}, {8, 1}, {8, 0}}; + + FormatInformation bestFI; + PerspectiveTransform bestPT; + + for (int i = 0; i < 4; ++i) { + auto mod2Pix = PerspectiveTransform(srcQuad, RotatedCorners(*fpQuad, i)); + + auto check = [&](int i, bool checkOne) { + auto p = mod2Pix(centered(FORMAT_INFO_COORDS[i])); + return image.isIn(p) && (!checkOne || image.get(p)); + }; + + // check that we see both innermost timing pattern modules + if (!check(0, true) || !check(8, false) || !check(16, true)) + continue; + + int formatInfoBits = 0; + for (int i = 1; i <= 15; ++i) + AppendBit(formatInfoBits, image.get(mod2Pix(centered(FORMAT_INFO_COORDS[i])))); + + auto fi = FormatInformation::DecodeMQR(formatInfoBits); + if (fi.hammingDistance < bestFI.hammingDistance) { + bestFI = fi; + bestPT = mod2Pix; + } + } + + if (!bestFI.isValid()) + return {}; + + const int dim = Version::DimensionOfVersion(bestFI.microVersion, true); + + // check that we are in fact not looking at a corner of a non-micro QRCode symbol + // we accept at most 1/3rd black pixels in the quite zone (in a QRCode symbol we expect about 1/2). + int blackPixels = 0; + for (int i = 0; i < dim; ++i) { + auto px = bestPT(centered(PointI{i, dim})); + auto py = bestPT(centered(PointI{dim, i})); + blackPixels += (image.isIn(px) && image.get(px)) + (image.isIn(py) && image.get(py)); + } + if (blackPixels > 2 * dim / 3) + return {}; + + return SampleGrid(image, dim, dim, bestPT); +} \ No newline at end of file diff --git a/src/qrcode/decoder/version.rs b/src/qrcode/decoder/version.rs index cb9828c..d739fd8 100755 --- a/src/qrcode/decoder/version.rs +++ b/src/qrcode/decoder/version.rs @@ -89,15 +89,15 @@ impl Version { 17 + 4 * self.versionNumber } - pub fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 { + 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 { todo!() } - pub fn DimensionStep(is_micro: bool) -> u32 { + pub const fn DimensionStep(is_micro: bool) -> u32 { todo!() } From 3d75de7183867fa4ef876fa2871974b93b1f6050 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 20 Mar 2023 11:09:06 -0500 Subject: [PATCH 13/72] DetectPureQR ported --- src/common/bit_matrix.rs | 27 ---- src/common/cpp_essentials/bitmatrix.rs | 61 +++++++++ .../cpp_essentials/bitmatrix_cursor_trait.rs | 26 ++-- src/common/cpp_essentials/mod.rs | 1 + src/common/cpp_essentials/pattern.rs | 6 + src/qrcode/cpp_port/detector.rs | 127 ++++++++++++------ src/rxing_result_point.rs | 4 +- tests/cropped_transposed_luma.rs | 2 +- 8 files changed, 168 insertions(+), 86 deletions(-) create mode 100644 src/common/cpp_essentials/bitmatrix.rs diff --git a/src/common/bit_matrix.rs b/src/common/bit_matrix.rs index 975219b..0868d7b 100644 --- a/src/common/bit_matrix.rs +++ b/src/common/bit_matrix.rs @@ -559,33 +559,6 @@ impl BitMatrix { Some([left, top, right - left + 1, bottom - top + 1]) } - pub fn - findBoundingBox(&self, left:&mut u32, top:&mut u32, width:&mut u32, height:&mut u32, minSize:u32) -> bool - { - todo!() - // let right; - // let bottom; - // if (!self.getTopLeftOnBitWithPosition(left, top) || !self.getBottomRightOnBitWithPosition(right, bottom) || bottom - top + 1 < minSize) - // {return false;} - - // for (int y = top; y <= bottom; y++ ) { - // for (int x = 0; x < left; ++x) - // if (get(x, y)) { - // left = x; - // break; - // } - // for (int x = _width-1; x > right; x--) - // if (get(x, y)) { - // right = x; - // break; - // } - // } - - // width = right - left + 1; - // height = bottom - top + 1; - // return width >= minSize && height >= minSize; - } - /** * This is useful in detecting a corner of a 'pure' barcode. * diff --git a/src/common/cpp_essentials/bitmatrix.rs b/src/common/cpp_essentials/bitmatrix.rs new file mode 100644 index 0000000..28619d8 --- /dev/null +++ b/src/common/cpp_essentials/bitmatrix.rs @@ -0,0 +1,61 @@ +use crate::common::BitMatrix; +use crate::common::Result; +use crate::point; + +impl BitMatrix { + pub fn Deflate( + &self, + width: u32, + height: u32, + top: f32, + left: f32, + subSampling: f32, + ) -> Result { + let mut result = BitMatrix::new(width, height)?; + + for y in 0..result.height() { + // for (int y = 0; y < result.height(); y++) { + let yOffset = top + y as f32 * subSampling; + for x in 0..result.width() { + // for (int x = 0; x < result.width(); x++) { + if (self.get_point(point(left + x as f32 * subSampling, yOffset))) { + result.set(x, y); + } + } + } + + Ok(result) + } + + pub fn findBoundingBox( + &self, + left: &mut u32, + top: &mut u32, + width: &mut u32, + height: &mut u32, + minSize: u32, + ) -> bool { + todo!() + // let right; + // let bottom; + // if (!self.getTopLeftOnBitWithPosition(left, top) || !self.getBottomRightOnBitWithPosition(right, bottom) || bottom - top + 1 < minSize) + // {return false;} + + // for (int y = top; y <= bottom; y++ ) { + // for (int x = 0; x < left; ++x) + // if (get(x, y)) { + // left = x; + // break; + // } + // for (int x = _width-1; x > right; x--) + // if (get(x, y)) { + // right = x; + // break; + // } + // } + + // width = right - left + 1; + // height = bottom - top + 1; + // return width >= minSize && height >= minSize; + } +} diff --git a/src/common/cpp_essentials/bitmatrix_cursor_trait.rs b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs index 133e19a..8f68b47 100644 --- a/src/common/cpp_essentials/bitmatrix_cursor_trait.rs +++ b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs @@ -165,27 +165,27 @@ pub trait BitMatrixCursorTrait { fn d(&self) -> Point; - - fn readPattern(&self, range : Option) -> Option - { + fn readPattern(&self, range: Option) -> Option { // let range = range.unwrap_or(0); - // let mut res :T; + // let mut res :T; // for i in res.into_iter() { // i = self.stepToEdge(1, range); // } - // // for (auto& i : res) - // // i = stepToEdge(1, range); - // return res; + // // for (auto& i : res) + // // i = stepToEdge(1, range); + // return res; todo!() } - - fn readPatternFromBlack(&self, maxWhitePrefix:i32, range: Option) -> Option - { + fn readPatternFromBlack(&self, maxWhitePrefix: i32, range: Option) -> Option { let range = range.unwrap_or(0); - if (maxWhitePrefix != 0 && self.isWhite() && !self.stepToEdge(Some(1), Some(maxWhitePrefix), None) > 0) - {return None;} - // return readPattern(range); + if (maxWhitePrefix != 0 + && self.isWhite() + && !self.stepToEdge(Some(1), Some(maxWhitePrefix), None) > 0) + { + return None; + } + // return readPattern(range); self.readPattern(Some(range)) } } diff --git a/src/common/cpp_essentials/mod.rs b/src/common/cpp_essentials/mod.rs index 4d85f66..40ad497 100644 --- a/src/common/cpp_essentials/mod.rs +++ b/src/common/cpp_essentials/mod.rs @@ -1,3 +1,4 @@ +mod bitmatrix; pub mod bitmatrix_cursor; pub mod bitmatrix_cursor_trait; pub mod concentric_finder; diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index b24915d..be4aef6 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -54,6 +54,12 @@ impl std::ops::IndexMut for PatternRow { } } +impl From> for PatternRow { + fn from(value: Vec) -> Self { + Self(value) + } +} + impl<'a> Iterator for PatternView<'_> { type Item = PatternType; diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index 4f8997b..24c8e5f 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -750,61 +750,102 @@ pub fn SampleQR(image: &BitMatrix, fp: &FinderPatternSet) -> Result Result -{ - type Pattern = Vec; +pub fn DetectPureQR(image: &BitMatrix) -> Result { + type Pattern = Vec; -// #ifdef PRINT_DEBUG -// SaveAsPBM(image, "weg.pbm"); -// #endif + // #ifdef PRINT_DEBUG + // SaveAsPBM(image, "weg.pbm"); + // #endif - const MIN_MODULES: u32 = Version::DimensionOfVersion(1, false); - const MAX_MODULES:u32 = Version::DimensionOfVersion(40, false); + const MIN_MODULES: u32 = Version::DimensionOfVersion(1, false); + const MAX_MODULES: u32 = Version::DimensionOfVersion(40, false); - let left; + let left; let top; let width; let height; - if (!image.findBoundingBox(left, top, width, height, MIN_MODULES) || std::abs(width - height) > 1) - {return Err(Exceptions::NOT_FOUND)} - let right = left + width - 1; - let bottom = top + height - 1; + if (!image.findBoundingBox(left, top, width, height, MIN_MODULES) + || (*width as i32 - *height as i32).abs() > 1) + { + 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 bl = point_i(*left, *bottom); - let diagonal : Pattern; - // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts - for [p,d] in [[tl, point_i(1,1)], [tr, point(-1.0,1.0)], [bl, point(1.0,-1.0)]] { - // for (auto [p, d] : {std::pair(tl, PointI{1, 1}), {tr, {-1, 1}}, {bl, {1, -1}}}) { - diagonal = EdgeTracer::new(image, p, d).readPatternFromBlack(1, width / 3 + 1).ok_or(Exceptions::NOT_FOUND)?; - // diagonal = BitMatrixCursorI(image, p, d).readPatternFromBlack(1, width / 3 + 1); - if (!IsPattern(diagonal, PATTERN) != 0.0) - {return Err(Exceptions::NOT_FOUND);} - } + let tl = point_i(*left, *top); + let tr = point_i(right, *top); + let bl = point_i(*left, bottom); + let diagonal: Pattern; + // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts + for [p, d] in [ + [tl, point_i(1, 1)], + [tr, point(-1.0, 1.0)], + [bl, point(1.0, -1.0)], + ] { + // for (auto [p, d] : {std::pair(tl, PointI{1, 1}), {tr, {-1, 1}}, {bl, {1, -1}}}) { + diagonal = EdgeTracer::new(image, p, d) + .readPatternFromBlack(1, Some(*width / 3 + 1)) + .ok_or(Exceptions::NOT_FOUND)?; + // diagonal = BitMatrixCursorI(image, p, d).readPatternFromBlack(1, width / 3 + 1); + let view = PatternView::new(&diagonal.into()); + if (!(IsPattern(&view, &PATTERN, None, 0.0, 0.0, None) != 0.0)) { + return Err(Exceptions::NOT_FOUND); + } + } - let fpWidth = Reduce(diagonal); - let dimension = - EstimateDimension(image, {tl + fpWidth / 2 * PointF(1, 1), fpWidth}, {tr + fpWidth / 2 * PointF(-1, 1), fpWidth}).dim; + let fpWidth = diagonal.iter().sum::() as i32; //Reduce(diagonal); + let dimension = EstimateDimension( + image, + ConcentricPattern { + p: tl + fpWidth as f32 / 2.0 * point_i(1, 1), + size: fpWidth, + }, + ConcentricPattern { + p: tr + fpWidth as f32 / 2.0 * point(-1.0, 1.0), + size: fpWidth, + }, + ) + .dim; - let moduleSize:f32 = float(width) / dimension; - if (dimension < MIN_MODULES || dimension > MAX_MODULES || - !image.isIn(PointF{left + moduleSize / 2 + (dimension - 1) * moduleSize, - top + moduleSize / 2 + (dimension - 1) * moduleSize})) - {return Err(Exceptions::NOT_FOUND);} + let moduleSize: f32 = ((*width) as f32) / dimension as f32; + if (dimension < MIN_MODULES as i32 + || dimension > MAX_MODULES as i32 + || !image.is_in(point( + *left as f32 + moduleSize / 2.0 + (dimension - 1) as f32 * moduleSize as f32, + *top as f32 + moduleSize / 2.0 + (dimension - 1) as f32 * moduleSize, + ))) + { + return Err(Exceptions::NOT_FOUND); + } -// #ifdef PRINT_DEBUG -// LogMatrix log; -// LogMatrixWriter lmw(log, image, 5, "grid2.pnm"); -// for (int y = 0; y < dimension; y++) -// for (int x = 0; x < dimension; x++) -// log(PointF(left + (x + .5f) * moduleSize, top + (y + .5f) * moduleSize)); -// #endif + // #ifdef PRINT_DEBUG + // LogMatrix log; + // LogMatrixWriter lmw(log, image, 5, "grid2.pnm"); + // for (int y = 0; y < dimension; y++) + // for (int x = 0; x < dimension; x++) + // log(PointF(left + (x + .5f) * moduleSize, top + (y + .5f) * moduleSize)); + // #endif - // Now just read off the bits (this is a crop + subsample) - return {Deflate(image, dimension, dimension, top + moduleSize / 2, left + moduleSize / 2, moduleSize), - {{left, top}, {right, top}, {right, bottom}, {left, bottom}}}; + // Now just read off the bits (this is a crop + subsample) + Ok(QRCodeDetectorResult { + bit_source: image.Deflate( + dimension as u32, + dimension as u32, + *top as f32 + moduleSize / 2.0, + *left as f32 + moduleSize / 2.0, + moduleSize, + )?, + result_points: vec![ + point_i(*left, *top), + point_i(right, *top), + point_i(right, bottom), + point_i(*left, bottom), + ], + }) + + // return {Deflate(image, dimension, dimension, top + moduleSize / 2, left + moduleSize / 2, moduleSize), + // {{left, top}, {right, top}, {right, bottom}, {left, bottom}}}; } pub fn DetectPureMQR( image:&BitMatrix) -> Result diff --git a/src/rxing_result_point.rs b/src/rxing_result_point.rs index 14a7db6..e589c8b 100644 --- a/src/rxing_result_point.rs +++ b/src/rxing_result_point.rs @@ -29,8 +29,8 @@ pub fn point_g>(x: T, y: T) -> Option { Some(Point::new(x.try_into().ok()?, y.try_into().ok()?)) } -pub fn point_i(x: u32, y: u32) -> Point { - Point::new(x as f32, y as f32) +pub fn point_i>(x: T, y: T) -> Point { + Point::new(x.into() as f32, y.into() as f32) } /** Currently necessary because the external OneDReader proc macro uses it. */ diff --git a/tests/cropped_transposed_luma.rs b/tests/cropped_transposed_luma.rs index f5747e2..af2382d 100644 --- a/tests/cropped_transposed_luma.rs +++ b/tests/cropped_transposed_luma.rs @@ -3,7 +3,7 @@ fn test_binarizer_init_empty() { assert!(rxing::helpers::detect_multiple_in_luma(DATA.to_vec(), 665, 286).is_ok()) } -static DATA: [u8; 190190] = [ +const DATA: [u8; 190190] = [ 220, 224, 216, 219, 221, 223, 221, 217, 216, 212, 203, 207, 213, 214, 208, 209, 218, 223, 221, 218, 212, 210, 202, 204, 209, 203, 211, 212, 216, 220, 223, 223, 221, 220, 219, 219, 219, 217, 214, 213, 215, 215, 213, 214, 214, 209, 203, 204, 212, 219, 221, 220, 225, 221, 208, 202, 205, From 0df86d4fd47bd698db0d8301bd569dccae26561d Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 20 Mar 2023 12:12:25 -0500 Subject: [PATCH 14/72] detector builds but does not work --- src/common/cpp_essentials/bitmatrix.rs | 10 +- .../cpp_essentials/bitmatrix_cursor_trait.rs | 2 +- src/common/cpp_essentials/mod.rs | 1 + .../cpp_essentials/qr_formatinformation.rs | 22 ++ src/qrcode/cpp_port/detector.rs | 288 +++++++++++------- src/qrcode/decoder/error_correction_level.rs | 3 + src/qrcode/decoder/format_information.rs | 19 +- src/qrcode/decoder/version.rs | 10 +- 8 files changed, 233 insertions(+), 122 deletions(-) create mode 100644 src/common/cpp_essentials/qr_formatinformation.rs diff --git a/src/common/cpp_essentials/bitmatrix.rs b/src/common/cpp_essentials/bitmatrix.rs index 28619d8..d0d3fc6 100644 --- a/src/common/cpp_essentials/bitmatrix.rs +++ b/src/common/cpp_essentials/bitmatrix.rs @@ -29,12 +29,12 @@ impl BitMatrix { pub fn findBoundingBox( &self, - left: &mut u32, - top: &mut u32, - width: &mut u32, - height: &mut u32, + left: u32, + top: u32, + width: u32, + height: u32, minSize: u32, - ) -> bool { + ) -> (bool, u32, u32, u32, u32) { todo!() // let right; // let bottom; diff --git a/src/common/cpp_essentials/bitmatrix_cursor_trait.rs b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs index 8f68b47..cc72746 100644 --- a/src/common/cpp_essentials/bitmatrix_cursor_trait.rs +++ b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs @@ -177,7 +177,7 @@ pub trait BitMatrixCursorTrait { todo!() } - fn readPatternFromBlack(&self, maxWhitePrefix: i32, range: Option) -> Option { + fn readPatternFromBlack(&mut self, maxWhitePrefix: i32, range: Option) -> Option { let range = range.unwrap_or(0); if (maxWhitePrefix != 0 && self.isWhite() diff --git a/src/common/cpp_essentials/mod.rs b/src/common/cpp_essentials/mod.rs index 40ad497..53bd356 100644 --- a/src/common/cpp_essentials/mod.rs +++ b/src/common/cpp_essentials/mod.rs @@ -8,6 +8,7 @@ pub mod edge_tracer; pub mod fast_edge_to_edge_counter; pub mod matrix; pub mod pattern; +mod qr_formatinformation; pub mod regression_line; pub mod regression_line_trait; pub mod step_result; diff --git a/src/common/cpp_essentials/qr_formatinformation.rs b/src/common/cpp_essentials/qr_formatinformation.rs new file mode 100644 index 0000000..149c838 --- /dev/null +++ b/src/common/cpp_essentials/qr_formatinformation.rs @@ -0,0 +1,22 @@ +use crate::qrcode::decoder::FormatInformation; + +impl FormatInformation { + pub fn DecodeMQR(formatInfoBits: u32) -> Self { + unimplemented!() + // // We don't use the additional masking (with 0x4445) to work around potentially non complying MicroQRCode encoders + // auto fi = FindBestFormatInfo(0, FORMAT_INFO_DECODE_LOOKUP_MICRO, {formatInfoBits, MirrorBits(formatInfoBits)}); + + // constexpr uint8_t BITS_TO_VERSION[] = {1, 2, 2, 3, 3, 4, 4, 4}; + + // // Bits 2/3/4 contain both error correction level and version, 0/1 contain mask. + // fi.ecLevel = ECLevelFromBits((fi.index >> 2) & 0x07, true); + // fi.dataMask = static_cast(fi.index & 0x03); + // fi.microVersion = BITS_TO_VERSION[(fi.index >> 2) & 0x07]; + // fi.isMirrored = fi.bitsIndex == 1; + + // return fi; + } + pub fn isValid(&self) -> bool { + unimplemented!() + } +} diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index 24c8e5f..7f84ccb 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -5,7 +5,7 @@ use crate::{ }, dimension, point_g, point_i, qrcode::{ - decoder::{Version, VersionRef}, + decoder::{FormatInformation, Version, VersionRef}, detector::QRCodeDetectorResult, }, Exceptions, @@ -757,26 +757,21 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result { // SaveAsPBM(image, "weg.pbm"); // #endif - const MIN_MODULES: u32 = Version::DimensionOfVersion(1, false); - const MAX_MODULES: u32 = Version::DimensionOfVersion(40, false); + let MIN_MODULES: u32 = Version::DimensionOfVersion(1, false); + let MAX_MODULES: u32 = Version::DimensionOfVersion(40, false); - let left; - let top; - let width; - let height; + let (found, left, top, width, height) = image.findBoundingBox(0, 0, 0, 0, MIN_MODULES); - if (!image.findBoundingBox(left, top, width, height, MIN_MODULES) - || (*width as i32 - *height as i32).abs() > 1) - { + if (!found || (width as i32 - height as i32).abs() > 1) { return Err(Exceptions::NOT_FOUND); } - let right = *left + *width - 1; - let bottom = *top + *height - 1; + let right = left + width - 1; + let bottom = top + height - 1; - let tl = point_i(*left, *top); - let tr = point_i(right, *top); - let bl = point_i(*left, bottom); - let diagonal: Pattern; + let tl = point_i(left, top); + let tr = point_i(right, top); + let bl = point_i(left, bottom); + let mut diagonal: Pattern = Vec::default(); // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts for [p, d] in [ [tl, point_i(1, 1)], @@ -785,10 +780,11 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result { ] { // for (auto [p, d] : {std::pair(tl, PointI{1, 1}), {tr, {-1, 1}}, {bl, {1, -1}}}) { diagonal = EdgeTracer::new(image, p, d) - .readPatternFromBlack(1, Some(*width / 3 + 1)) + .readPatternFromBlack(1, Some(width / 3 + 1)) .ok_or(Exceptions::NOT_FOUND)?; // diagonal = BitMatrixCursorI(image, p, d).readPatternFromBlack(1, width / 3 + 1); - let view = PatternView::new(&diagonal.into()); + let diag_hld = diagonal.clone().into(); + let view = PatternView::new(&diag_hld); if (!(IsPattern(&view, &PATTERN, None, 0.0, 0.0, None) != 0.0)) { return Err(Exceptions::NOT_FOUND); } @@ -808,12 +804,12 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result { ) .dim; - let moduleSize: f32 = ((*width) as f32) / dimension as f32; + let moduleSize: f32 = ((width) as f32) / dimension as f32; if (dimension < MIN_MODULES as i32 || dimension > MAX_MODULES as i32 || !image.is_in(point( - *left as f32 + moduleSize / 2.0 + (dimension - 1) as f32 * moduleSize as f32, - *top as f32 + moduleSize / 2.0 + (dimension - 1) as f32 * moduleSize, + left as f32 + moduleSize / 2.0 + (dimension - 1) as f32 * moduleSize as f32, + top as f32 + moduleSize / 2.0 + (dimension - 1) as f32 * moduleSize, ))) { return Err(Exceptions::NOT_FOUND); @@ -828,123 +824,197 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result { // #endif // Now just read off the bits (this is a crop + subsample) - Ok(QRCodeDetectorResult { - bit_source: image.Deflate( + Ok(QRCodeDetectorResult::new( + image.Deflate( dimension as u32, dimension as u32, - *top as f32 + moduleSize / 2.0, - *left as f32 + moduleSize / 2.0, + top as f32 + moduleSize / 2.0, + left as f32 + moduleSize / 2.0, moduleSize, )?, - result_points: vec![ - point_i(*left, *top), - point_i(right, *top), + vec![ + point_i(left, top), + point_i(right, top), point_i(right, bottom), - point_i(*left, bottom), + point_i(left, bottom), ], - }) + )) // return {Deflate(image, dimension, dimension, top + moduleSize / 2, left + moduleSize / 2, moduleSize), // {{left, top}, {right, top}, {right, bottom}, {left, bottom}}}; } -pub fn DetectPureMQR( image:&BitMatrix) -> Result -{ - using Pattern = std::array; +pub fn DetectPureMQR(image: &BitMatrix) -> Result { + type Pattern = [PatternType; 5]; - constexpr int MIN_MODULES = Version::DimensionOfVersion(1, true); - constexpr int MAX_MODULES = Version::DimensionOfVersion(4, true); + let MIN_MODULES = Version::DimensionOfVersion(1, true); + let MAX_MODULES = Version::DimensionOfVersion(4, true); - int left, top, width, height; - if (!image.findBoundingBox(left, top, width, height, MIN_MODULES) || std::abs(width - height) > 1) - return {}; - int right = left + width - 1; - int bottom = top + height - 1; + let (found, left, top, width, height) = image.findBoundingBox(0, 0, 0, 0, MIN_MODULES); - // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts - auto diagonal = BitMatrixCursorI(image, {left, top}, {1, 1}).readPatternFromBlack(1); - if (!IsPattern(diagonal, PATTERN)) - return {}; + // int left, top, width, height; + if (!found || (width as i32 - height as i32).abs() > 1) { + return Err(Exceptions::NOT_FOUND); + } + let right = left + width - 1; + let bottom = top + height - 1; - auto fpWidth = Reduce(diagonal); - float moduleSize = float(fpWidth) / 7; - int dimension = narrow_cast(std::lround(width / moduleSize)); + // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts + let diagonal = EdgeTracer::new(&image, point_i(left, top), point_i(1, 1)) + .readPatternFromBlack(1, None) + .ok_or(Exceptions::ILLEGAL_STATE)?; + if (!(IsPattern(diagonal, &PATTERN, None, 0.0, 0.0, None) != 0.0)) { + return Err(Exceptions::NOT_FOUND); + } - if (dimension < MIN_MODULES || dimension > MAX_MODULES || - !image.isIn(PointF{left + moduleSize / 2 + (dimension - 1) * moduleSize, - top + moduleSize / 2 + (dimension - 1) * moduleSize})) - return {}; + let fpWidth = (diagonal.into_iter().sum::()); + let moduleSize: f32 = (fpWidth as f32) / 7.0; + let dimension = (width as f32 / moduleSize).floor() as u32; -// #ifdef PRINT_DEBUG -// LogMatrix log; -// LogMatrixWriter lmw(log, image, 5, "grid2.pnm"); -// for (int y = 0; y < dimension; y++) -// for (int x = 0; x < dimension; x++) -// log(PointF(left + (x + .5f) * moduleSize, top + (y + .5f) * moduleSize)); -// #endif + if (dimension < MIN_MODULES + || dimension > MAX_MODULES + || !image.is_in(point( + left as f32 + moduleSize as f32 / 2.0 + (dimension - 1) as f32 * moduleSize, + top as f32 + moduleSize as f32 / 2.0 + (dimension - 1) as f32 * moduleSize, + ))) + { + return Err(Exceptions::NOT_FOUND); + } - // Now just read off the bits (this is a crop + subsample) - return {Deflate(image, dimension, dimension, top + moduleSize / 2, left + moduleSize / 2, moduleSize), - {{left, top}, {right, top}, {right, bottom}, {left, bottom}}}; + // #ifdef PRINT_DEBUG + // LogMatrix log; + // LogMatrixWriter lmw(log, image, 5, "grid2.pnm"); + // for (int y = 0; y < dimension; y++) + // for (int x = 0; x < dimension; 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( + dimension, + dimension, + top as f32 + moduleSize / 2.0, + left as f32 + moduleSize / 2.0, + moduleSize, + )?, + vec![ + point_i(left, top), + point_i(right, top), + point_i(right, bottom), + point_i(left, bottom), + ], + )) + // return {Deflate(image, dimension, dimension, top + moduleSize / 2, left + moduleSize / 2, moduleSize), + // {{left, top}, {right, top}, {right, bottom}, {left, bottom}}}; } -pub fn SampleMQR( image:&BitMatrix, fp:ConcentricPattern) -> Result -{ - auto fpQuad = FindConcentricPatternCorners(image, fp, fp.size, 2); - if (!fpQuad) - return {}; +pub fn SampleMQR(image: &BitMatrix, fp: ConcentricPattern) -> Result { + let Some(fpQuad) = FindConcentricPatternCorners(image, fp.p, fp.size, 2) else { + return Err(Exceptions::NOT_FOUND); + }; - auto srcQuad = Rectangle(7, 7, 0.5); + let srcQuad = Quadrilateral::rectangle(7, 7, Some(0.5)); -// #if defined(_MSVC_LANG) // TODO: see MSVC issue https://developercommunity.visualstudio.com/t/constexpr-object-is-unable-to-be-used-as/10035065 -// static -// #else -// constexpr -// #endif - const PointI FORMAT_INFO_COORDS[] = {{0, 8}, {1, 8}, {2, 8}, {3, 8}, {4, 8}, {5, 8}, {6, 8}, {7, 8}, {8, 8}, - {8, 7}, {8, 6}, {8, 5}, {8, 4}, {8, 3}, {8, 2}, {8, 1}, {8, 0}}; + // #if defined(_MSVC_LANG) // TODO: see MSVC issue https://developercommunity.visualstudio.com/t/constexpr-object-is-unable-to-be-used-as/10035065 + // static + // #else + // constexpr + // #endif + let FORMAT_INFO_COORDS: [Point; 17] = [ + point_i(0, 8), + point_i(1, 8), + point_i(2, 8), + point_i(3, 8), + point_i(4, 8), + point_i(5, 8), + point_i(6, 8), + point_i(7, 8), + point_i(8, 8), + point_i(8, 7), + point_i(8, 6), + point_i(8, 5), + point_i(8, 4), + point_i(8, 3), + point_i(8, 2), + point_i(8, 1), + point_i(8, 0), + ]; - FormatInformation bestFI; - PerspectiveTransform bestPT; + let mut bestFI = FormatInformation::default(); + let mut bestPT = PerspectiveTransform::quadrilateralToQuadrilateral( + srcQuad, + fpQuad.rotated_corners(Some(0), None), + )?; - for (int i = 0; i < 4; ++i) { - auto mod2Pix = PerspectiveTransform(srcQuad, RotatedCorners(*fpQuad, i)); + for i in 0..4 { + // for (int i = 0; i < 4; ++i) { + let mod2Pix = PerspectiveTransform::quadrilateralToQuadrilateral( + srcQuad, + fpQuad.rotated_corners(Some(i), None), + )?; - auto check = [&](int i, bool checkOne) { - auto p = mod2Pix(centered(FORMAT_INFO_COORDS[i])); - return image.isIn(p) && (!checkOne || image.get(p)); - }; + let check = |i, checkOne: bool| { + let p = mod2Pix.transform_point(Point::centered(FORMAT_INFO_COORDS[i])); + return image.is_in(p) && (!checkOne || image.get_point(p)); + }; - // check that we see both innermost timing pattern modules - if (!check(0, true) || !check(8, false) || !check(16, true)) - continue; + // check that we see both innermost timing pattern modules + if (!check(0, true) || !check(8, false) || !check(16, true)) { + continue; + } - int formatInfoBits = 0; - for (int i = 1; i <= 15; ++i) - AppendBit(formatInfoBits, image.get(mod2Pix(centered(FORMAT_INFO_COORDS[i])))); + let mut formatInfoBits = 0; + for i in 1..=15 + // for (int i = 1; i <= 15; ++i) + { + AppendBit( + &mut formatInfoBits, + image.get_point(mod2Pix.transform_point(Point::centered(FORMAT_INFO_COORDS[i]))), + ); + } - auto fi = FormatInformation::DecodeMQR(formatInfoBits); - if (fi.hammingDistance < bestFI.hammingDistance) { - bestFI = fi; - bestPT = mod2Pix; - } - } + let fi = FormatInformation::DecodeMQR(formatInfoBits as u32); + if (fi.hammingDistance < bestFI.hammingDistance) { + bestFI = fi; + bestPT = mod2Pix; + } + } - if (!bestFI.isValid()) - return {}; + if (!bestFI.isValid()) { + return Err(Exceptions::NOT_FOUND); + } - const int dim = Version::DimensionOfVersion(bestFI.microVersion, true); + let dim: u32 = Version::DimensionOfVersion(bestFI.microVersion, true); - // check that we are in fact not looking at a corner of a non-micro QRCode symbol - // we accept at most 1/3rd black pixels in the quite zone (in a QRCode symbol we expect about 1/2). - int blackPixels = 0; - for (int i = 0; i < dim; ++i) { - auto px = bestPT(centered(PointI{i, dim})); - auto py = bestPT(centered(PointI{dim, i})); - blackPixels += (image.isIn(px) && image.get(px)) + (image.isIn(py) && image.get(py)); - } - if (blackPixels > 2 * dim / 3) - return {}; + // check that we are in fact not looking at a corner of a non-micro QRCode symbol + // we accept at most 1/3rd black pixels in the quite zone (in a QRCode symbol we expect about 1/2). + let mut blackPixels = 0; + for i in 0..dim { + // for (int i = 0; i < dim; ++i) { + let px = bestPT.transform_point(Point::centered(point_i(i, dim))); + let py = bestPT.transform_point(Point::centered(point_i(dim, i))); + blackPixels += u32::from((image.is_in(px) && image.get_point(px))) + + u32::from((image.is_in(py) && image.get_point(py))); + } + if (blackPixels > 2 * dim / 3) { + return Err(Exceptions::NOT_FOUND); + } - return SampleGrid(image, dim, dim, bestPT); -} \ No newline at end of file + let grid_sampler = DefaultGridSampler::default(); + Ok(QRCodeDetectorResult::new( + grid_sampler.sample_grid( + image, + dim, + dim, + &[SamplerControl { + p0: point_i(0, dim as u32), + p1: point_i(0, dim as u32), + transform: bestPT, + }], + )?, + Vec::default(), + )) + + // SampleGrid(image, dim, dim, bestPT) +} diff --git a/src/qrcode/decoder/error_correction_level.rs b/src/qrcode/decoder/error_correction_level.rs index 9b61495..0d5c734 100644 --- a/src/qrcode/decoder/error_correction_level.rs +++ b/src/qrcode/decoder/error_correction_level.rs @@ -35,6 +35,7 @@ pub enum ErrorCorrectionLevel { Q, //0x03 /** H = ~30% correction */ H, //0x02 + Invalid, } impl ErrorCorrectionLevel { @@ -60,6 +61,7 @@ impl ErrorCorrectionLevel { ErrorCorrectionLevel::M => 0x00, ErrorCorrectionLevel::Q => 0x03, ErrorCorrectionLevel::H => 0x02, + ErrorCorrectionLevel::Invalid => 0x00, } } @@ -69,6 +71,7 @@ impl ErrorCorrectionLevel { ErrorCorrectionLevel::M => 1, ErrorCorrectionLevel::Q => 2, ErrorCorrectionLevel::H => 3, + ErrorCorrectionLevel::Invalid => 100, } } } diff --git a/src/qrcode/decoder/format_information.rs b/src/qrcode/decoder/format_information.rs index b6564f4..c0256a6 100644 --- a/src/qrcode/decoder/format_information.rs +++ b/src/qrcode/decoder/format_information.rs @@ -68,8 +68,21 @@ const FORMAT_INFO_DECODE_LOOKUP: [[u32; 2]; 32] = [ */ #[derive(Hash, Eq, PartialEq, Debug)] pub struct FormatInformation { - error_correction_level: ErrorCorrectionLevel, - data_mask: u8, + pub hammingDistance: u32, + pub error_correction_level: ErrorCorrectionLevel, + pub data_mask: u8, + pub microVersion: u32, +} + +impl Default for FormatInformation { + fn default() -> Self { + Self { + hammingDistance: 255, + error_correction_level: ErrorCorrectionLevel::Invalid, + data_mask: Default::default(), + microVersion: 0, + } + } } impl FormatInformation { @@ -79,6 +92,8 @@ impl FormatInformation { // Bottom 3 bits let dataMask = format_info & 0x07; Ok(Self { + hammingDistance: 255, + microVersion: 0, error_correction_level: errorCorrectionLevel, data_mask: dataMask, }) diff --git a/src/qrcode/decoder/version.rs b/src/qrcode/decoder/version.rs index d739fd8..d682158 100755 --- a/src/qrcode/decoder/version.rs +++ b/src/qrcode/decoder/version.rs @@ -89,16 +89,16 @@ impl Version { 17 + 4 * self.versionNumber } - pub const fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 { + pub fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 { Self::DimensionOffset(is_micro) + Self::DimensionStep(is_micro) * version } - pub const fn DimensionOffset(is_micro: bool) -> u32 { - todo!() + pub fn DimensionOffset(is_micro: bool) -> u32 { + unimplemented!() } - pub const fn DimensionStep(is_micro: bool) -> u32 { - todo!() + pub fn DimensionStep(is_micro: bool) -> u32 { + unimplemented!() } pub fn getECBlocksForLevel(&self, ecLevel: ErrorCorrectionLevel) -> &ECBlocks { From 372bb454c0a4d3bf3e980c49d3afd29aaa03f763 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 20 Mar 2023 15:33:01 -0500 Subject: [PATCH 15/72] reorganize some impl blocks --- .../{ => base_extentions}/bitmatrix.rs | 0 .../cpp_essentials/base_extentions/mod.rs | 3 ++ .../qr_formatinformation.rs | 0 .../base_extentions/qrcode_version.rs | 47 +++++++++++++++++++ src/common/cpp_essentials/mod.rs | 4 +- src/qrcode/decoder/version.rs | 45 +----------------- 6 files changed, 53 insertions(+), 46 deletions(-) rename src/common/cpp_essentials/{ => base_extentions}/bitmatrix.rs (100%) create mode 100644 src/common/cpp_essentials/base_extentions/mod.rs rename src/common/cpp_essentials/{ => base_extentions}/qr_formatinformation.rs (100%) create mode 100644 src/common/cpp_essentials/base_extentions/qrcode_version.rs diff --git a/src/common/cpp_essentials/bitmatrix.rs b/src/common/cpp_essentials/base_extentions/bitmatrix.rs similarity index 100% rename from src/common/cpp_essentials/bitmatrix.rs rename to src/common/cpp_essentials/base_extentions/bitmatrix.rs diff --git a/src/common/cpp_essentials/base_extentions/mod.rs b/src/common/cpp_essentials/base_extentions/mod.rs new file mode 100644 index 0000000..06e69f7 --- /dev/null +++ b/src/common/cpp_essentials/base_extentions/mod.rs @@ -0,0 +1,3 @@ +mod bitmatrix; +mod qr_formatinformation; +mod qrcode_version; diff --git a/src/common/cpp_essentials/qr_formatinformation.rs b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs similarity index 100% rename from src/common/cpp_essentials/qr_formatinformation.rs rename to src/common/cpp_essentials/base_extentions/qr_formatinformation.rs diff --git a/src/common/cpp_essentials/base_extentions/qrcode_version.rs b/src/common/cpp_essentials/base_extentions/qrcode_version.rs new file mode 100644 index 0000000..3944417 --- /dev/null +++ b/src/common/cpp_essentials/base_extentions/qrcode_version.rs @@ -0,0 +1,47 @@ +use crate::common::Result; +use crate::qrcode::decoder::{Version, VersionRef, VERSION_DECODE_INFO}; +use crate::Exceptions; + +impl Version { + pub fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 { + Self::DimensionOffset(is_micro) + Self::DimensionStep(is_micro) * version + } + + pub fn DimensionOffset(is_micro: bool) -> u32 { + unimplemented!() + } + + pub fn DimensionStep(is_micro: bool) -> u32 { + unimplemented!() + } + pub fn DecodeVersionInformation(versionBitsA: i32, versionBitsB: i32) -> Result { + let mut bestDifference = u32::MAX; + let mut bestVersion = 0; + let mut i = 0; + for targetVersion in VERSION_DECODE_INFO { + // for (int targetVersion : VERSION_DECODE_INFO) { + // Do the version info bits match exactly? done. + if targetVersion == versionBitsA as u32 || targetVersion == versionBitsB as u32 { + return Self::getVersionForNumber(i + 7); + } + // Otherwise see if this is the closest to a real version info bit string + // we have seen so far + for bits in [versionBitsA, versionBitsB] { + // for (int bits : {versionBitsA, versionBitsB}) { + let bitsDifference = ((bits as u32) ^ targetVersion).count_ones(); //BitHacks::CountBitsSet(bits ^ targetVersion); + if bitsDifference < bestDifference { + bestVersion = i + 7; + bestDifference = bitsDifference; + } + } + i += 1; + } + // We can tolerate up to 3 bits of error since no two version info codewords will + // differ in less than 8 bits. + if bestDifference <= 3 { + return Self::getVersionForNumber(bestVersion); + } + // If we didn't find a close enough match, fail + return Err(Exceptions::ILLEGAL_STATE); + } +} diff --git a/src/common/cpp_essentials/mod.rs b/src/common/cpp_essentials/mod.rs index 53bd356..817e38b 100644 --- a/src/common/cpp_essentials/mod.rs +++ b/src/common/cpp_essentials/mod.rs @@ -1,4 +1,5 @@ -mod bitmatrix; +mod base_extentions; + pub mod bitmatrix_cursor; pub mod bitmatrix_cursor_trait; pub mod concentric_finder; @@ -8,7 +9,6 @@ pub mod edge_tracer; pub mod fast_edge_to_edge_counter; pub mod matrix; pub mod pattern; -mod qr_formatinformation; pub mod regression_line; pub mod regression_line_trait; pub mod step_result; diff --git a/src/qrcode/decoder/version.rs b/src/qrcode/decoder/version.rs index d682158..461d0c6 100755 --- a/src/qrcode/decoder/version.rs +++ b/src/qrcode/decoder/version.rs @@ -33,7 +33,7 @@ static VERSIONS: Lazy> = Lazy::new(Version::buildVersions); * See ISO 18004:2006 Annex D. * Element i represents the raw version bits that specify version i + 7 */ -const VERSION_DECODE_INFO: [u32; 34] = [ +pub const VERSION_DECODE_INFO: [u32; 34] = [ 0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, 0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78, 0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB, 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B, @@ -89,18 +89,6 @@ impl Version { 17 + 4 * self.versionNumber } - pub fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 { - Self::DimensionOffset(is_micro) + Self::DimensionStep(is_micro) * version - } - - pub fn DimensionOffset(is_micro: bool) -> u32 { - unimplemented!() - } - - pub fn DimensionStep(is_micro: bool) -> u32 { - unimplemented!() - } - pub fn getECBlocksForLevel(&self, ecLevel: ErrorCorrectionLevel) -> &ECBlocks { &self.ecBlocks[ecLevel.get_ordinal() as usize] } @@ -152,37 +140,6 @@ impl Version { Err(Exceptions::NOT_FOUND) } - pub fn DecodeVersionInformation(versionBitsA: i32, versionBitsB: i32) -> Result { - let mut bestDifference = u32::MAX; - let mut bestVersion = 0; - let mut i = 0; - for targetVersion in VERSION_DECODE_INFO { - // for (int targetVersion : VERSION_DECODE_INFO) { - // Do the version info bits match exactly? done. - if targetVersion == versionBitsA as u32 || targetVersion == versionBitsB as u32 { - return Self::getVersionForNumber(i + 7); - } - // Otherwise see if this is the closest to a real version info bit string - // we have seen so far - for bits in [versionBitsA, versionBitsB] { - // for (int bits : {versionBitsA, versionBitsB}) { - let bitsDifference = ((bits as u32) ^ targetVersion).count_ones(); //BitHacks::CountBitsSet(bits ^ targetVersion); - if bitsDifference < bestDifference { - bestVersion = i + 7; - bestDifference = bitsDifference; - } - } - i += 1; - } - // We can tolerate up to 3 bits of error since no two version info codewords will - // differ in less than 8 bits. - if bestDifference <= 3 { - return Self::getVersionForNumber(bestVersion); - } - // If we didn't find a close enough match, fail - return Err(Exceptions::ILLEGAL_STATE); - } - /** * See ISO 18004:2006 Annex E */ From f852e1433143fc85ec6ecf08dee29f40b8e259ec Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 20 Mar 2023 16:39:19 -0500 Subject: [PATCH 16/72] move some functions around --- .../base_extentions/bitmatrix.rs | 31 ++++++------------- src/qrcode/decoder/version.rs | 6 ++-- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/common/cpp_essentials/base_extentions/bitmatrix.rs b/src/common/cpp_essentials/base_extentions/bitmatrix.rs index d0d3fc6..95ebd37 100644 --- a/src/common/cpp_essentials/base_extentions/bitmatrix.rs +++ b/src/common/cpp_essentials/base_extentions/bitmatrix.rs @@ -18,7 +18,7 @@ impl BitMatrix { let yOffset = top + y as f32 * subSampling; for x in 0..result.width() { // for (int x = 0; x < result.width(); x++) { - if (self.get_point(point(left + x as f32 * subSampling, yOffset))) { + if self.get_point(point(left + x as f32 * subSampling, yOffset)) { result.set(x, y); } } @@ -27,6 +27,14 @@ impl BitMatrix { Ok(result) } + pub fn getTopLeftOnBitWithPosition(&self, left: u32, top: u32) -> (bool, u32, u32) { + todo!() + } + + pub fn getBottomRightOnBitWithPosition(&self, right: u32, bottom: u32) -> (bool, u32, u32) { + todo!() + } + pub fn findBoundingBox( &self, left: u32, @@ -36,26 +44,5 @@ impl BitMatrix { minSize: u32, ) -> (bool, u32, u32, u32, u32) { todo!() - // let right; - // let bottom; - // if (!self.getTopLeftOnBitWithPosition(left, top) || !self.getBottomRightOnBitWithPosition(right, bottom) || bottom - top + 1 < minSize) - // {return false;} - - // for (int y = top; y <= bottom; y++ ) { - // for (int x = 0; x < left; ++x) - // if (get(x, y)) { - // left = x; - // break; - // } - // for (int x = _width-1; x > right; x--) - // if (get(x, y)) { - // right = x; - // break; - // } - // } - - // width = right - left + 1; - // height = bottom - top + 1; - // return width >= minSize && height >= minSize; } } diff --git a/src/qrcode/decoder/version.rs b/src/qrcode/decoder/version.rs index 461d0c6..a325780 100755 --- a/src/qrcode/decoder/version.rs +++ b/src/qrcode/decoder/version.rs @@ -930,7 +930,7 @@ impl ECBlocks { } } - pub fn getECCodewordsPerBlock(&self) -> u32 { + pub const fn getECCodewordsPerBlock(&self) -> u32 { self.ecCodewordsPerBlock } @@ -970,11 +970,11 @@ impl ECB { } } - pub fn getCount(&self) -> u32 { + pub const fn getCount(&self) -> u32 { self.count } - pub fn getDataCodewords(&self) -> u32 { + pub const fn getDataCodewords(&self) -> u32 { self.dataCodewords } } From 638c2ac72ffb69f0601650a43c989eb0bc2fff44 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 20 Mar 2023 17:10:05 -0500 Subject: [PATCH 17/72] port DimensionOffset and DimensionStep --- .../cpp_essentials/base_extentions/qrcode_version.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/common/cpp_essentials/base_extentions/qrcode_version.rs b/src/common/cpp_essentials/base_extentions/qrcode_version.rs index 3944417..bcfe438 100644 --- a/src/common/cpp_essentials/base_extentions/qrcode_version.rs +++ b/src/common/cpp_essentials/base_extentions/qrcode_version.rs @@ -8,11 +8,19 @@ impl Version { } pub fn DimensionOffset(is_micro: bool) -> u32 { - unimplemented!() + match is_micro { + true => 9, + false => 17, + } + // return std::array{17, 9}[isMicro]; } pub fn DimensionStep(is_micro: bool) -> u32 { - unimplemented!() + match is_micro { + true => 2, + false => 4, + } + // return std::array{4, 2}[isMicro]; } pub fn DecodeVersionInformation(versionBitsA: i32, versionBitsB: i32) -> Result { let mut bestDifference = u32::MAX; From d183a326aaf91cf2be174c71a6ef1f98daaa8284 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 21 Mar 2023 09:19:52 -0500 Subject: [PATCH 18/72] in progress --- .../base_extentions/qrcode_version.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/common/cpp_essentials/base_extentions/qrcode_version.rs b/src/common/cpp_essentials/base_extentions/qrcode_version.rs index bcfe438..a63a2aa 100644 --- a/src/common/cpp_essentials/base_extentions/qrcode_version.rs +++ b/src/common/cpp_essentials/base_extentions/qrcode_version.rs @@ -2,6 +2,19 @@ use crate::common::Result; use crate::qrcode::decoder::{Version, VersionRef, VERSION_DECODE_INFO}; use crate::Exceptions; +// const Version* Version::AllMicroVersions() +// { +// /** +// * See ISO 18004:2006 6.5.1 Table 9 +// */ +// static const Version allVersions[] = { +// {1, {2, 1, 3, 0, 0}}, +// {2, {5, 1, 5, 0, 0, 6, 1, 4, 0, 0}}, +// {3, {6, 1, 11, 0, 0, 8, 1, 9, 0, 0}}, +// {4, {8, 1, 16, 0, 0, 10, 1, 14, 0, 0, 14, 1, 10, 0, 0}}}; +// return allVersions; +// } + impl Version { pub fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 { Self::DimensionOffset(is_micro) + Self::DimensionStep(is_micro) * version From 8486cba277926240023e7b3f633c8e2461c9e4cd Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 21 Mar 2023 11:22:31 -0500 Subject: [PATCH 19/72] port bitmatrix_curosor readpattern --- .../cpp_essentials/bitmatrix_cursor_trait.rs | 31 ++++++++++++------- src/qrcode/cpp_port/detector.rs | 14 +++++---- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/common/cpp_essentials/bitmatrix_cursor_trait.rs b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs index cc72746..7ac1d17 100644 --- a/src/common/cpp_essentials/bitmatrix_cursor_trait.rs +++ b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs @@ -165,19 +165,26 @@ pub trait BitMatrixCursorTrait { fn d(&self) -> Point; - fn readPattern(&self, range: Option) -> Option { - // let range = range.unwrap_or(0); - // let mut res :T; - // for i in res.into_iter() { - // i = self.stepToEdge(1, range); - // } - // // for (auto& i : res) - // // i = stepToEdge(1, range); - // return res; - todo!() + fn readPattern + Default + Copy + Clone>( + &mut self, + range: Option, + ) -> Option<[T; LEN]> { + let range = range.unwrap_or(0); + let mut res = [T::default(); LEN]; + for i in res.iter_mut() { + *i = self + .stepToEdge(Some(1), Some(range), None) + .try_into() + .ok()?; + } + Some(res) } - fn readPatternFromBlack(&mut self, maxWhitePrefix: i32, range: Option) -> Option { + fn readPatternFromBlack + Default + Copy + Clone>( + &mut self, + maxWhitePrefix: i32, + range: Option, + ) -> Option<[T; LEN]> { let range = range.unwrap_or(0); if (maxWhitePrefix != 0 && self.isWhite() @@ -186,6 +193,6 @@ pub trait BitMatrixCursorTrait { return None; } // return readPattern(range); - self.readPattern(Some(range)) + self.readPattern::(Some(range)) } } diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index 7f84ccb..d95fc64 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -751,7 +751,7 @@ pub fn SampleQR(image: &BitMatrix, fp: &FinderPatternSet) -> Result Result { - type Pattern = Vec; + type Pattern = [PatternType; 5]; // #ifdef PRINT_DEBUG // SaveAsPBM(image, "weg.pbm"); @@ -771,7 +771,7 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result { let tl = point_i(left, top); let tr = point_i(right, top); let bl = point_i(left, bottom); - let mut diagonal: Pattern = Vec::default(); + let mut diagonal: Pattern = Default::default(); // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts for [p, d] in [ [tl, point_i(1, 1)], @@ -780,10 +780,10 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result { ] { // for (auto [p, d] : {std::pair(tl, PointI{1, 1}), {tr, {-1, 1}}, {bl, {1, -1}}}) { diagonal = EdgeTracer::new(image, p, d) - .readPatternFromBlack(1, Some(width / 3 + 1)) + .readPatternFromBlack(1, Some((width / 3 + 1) as i32)) .ok_or(Exceptions::NOT_FOUND)?; // diagonal = BitMatrixCursorI(image, p, d).readPatternFromBlack(1, width / 3 + 1); - let diag_hld = diagonal.clone().into(); + let diag_hld = diagonal.to_vec().into(); let view = PatternView::new(&diag_hld); if (!(IsPattern(&view, &PATTERN, None, 0.0, 0.0, None) != 0.0)) { return Err(Exceptions::NOT_FOUND); @@ -860,10 +860,12 @@ pub fn DetectPureMQR(image: &BitMatrix) -> Result { let bottom = top + height - 1; // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts - let diagonal = EdgeTracer::new(&image, point_i(left, top), point_i(1, 1)) + let diagonal: Pattern = EdgeTracer::new(&image, point_i(left, top), point_i(1, 1)) .readPatternFromBlack(1, None) .ok_or(Exceptions::ILLEGAL_STATE)?; - if (!(IsPattern(diagonal, &PATTERN, None, 0.0, 0.0, None) != 0.0)) { + let diag_hld = diagonal.to_vec().into(); + let view = PatternView::new(&diag_hld); + if (!(IsPattern(&view, &PATTERN, None, 0.0, 0.0, None) != 0.0)) { return Err(Exceptions::NOT_FOUND); } From 7b6a80e4ac7411a11ca170ce7852faf2ea9b43ac Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 21 Mar 2023 11:26:30 -0500 Subject: [PATCH 20/72] move some todo!() to unimplemented!() --- .../base_extentions/bitmatrix.rs | 39 ++++++++++++++++++- src/oned/one_d_code_writer.rs | 2 +- src/oned/upc_ean_reader.rs | 10 ++--- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/common/cpp_essentials/base_extentions/bitmatrix.rs b/src/common/cpp_essentials/base_extentions/bitmatrix.rs index 95ebd37..174eef2 100644 --- a/src/common/cpp_essentials/base_extentions/bitmatrix.rs +++ b/src/common/cpp_essentials/base_extentions/bitmatrix.rs @@ -27,12 +27,27 @@ impl BitMatrix { Ok(result) } - pub fn getTopLeftOnBitWithPosition(&self, left: u32, top: u32) -> (bool, u32, u32) { + pub fn getTopLeftOnBitWithPosition(&self, left: &mut u32, top: &mut u32) -> bool { todo!() + // int bitsOffset = (int)std::distance(_bits.begin(), std::find_if(_bits.begin(), _bits.end(), isSet)); + // if (bitsOffset == Size(_bits)) { + // return false; + // } + // top = bitsOffset / _width; + // left = (bitsOffset % _width); + // return true; } - pub fn getBottomRightOnBitWithPosition(&self, right: u32, bottom: u32) -> (bool, u32, u32) { + pub fn getBottomRightOnBitWithPosition(&self, right: &mut u32, bottom: &mut u32) -> bool { todo!() + // int bitsOffset = Size(_bits) - 1 - (int)std::distance(_bits.rbegin(), std::find_if(_bits.rbegin(), _bits.rend(), isSet)); + // if (bitsOffset < 0) { + // return false; + // } + + // bottom = bitsOffset / _width; + // right = (bitsOffset % _width); + // return true; } pub fn findBoundingBox( @@ -44,5 +59,25 @@ impl BitMatrix { minSize: u32, ) -> (bool, u32, u32, u32, u32) { todo!() + // int right, bottom; + // if (!getTopLeftOnBit(left, top) || !getBottomRightOnBit(right, bottom) || bottom - top + 1 < minSize) + // return false; + + // for (int y = top; y <= bottom; y++ ) { + // for (int x = 0; x < left; ++x) + // if (get(x, y)) { + // left = x; + // break; + // } + // for (int x = _width-1; x > right; x--) + // if (get(x, y)) { + // right = x; + // break; + // } + // } + + // width = right - left + 1; + // height = bottom - top + 1; + // return width >= minSize && height >= minSize; } } diff --git a/src/oned/one_d_code_writer.rs b/src/oned/one_d_code_writer.rs index a73b59e..b57c589 100644 --- a/src/oned/one_d_code_writer.rs +++ b/src/oned/one_d_code_writer.rs @@ -195,6 +195,6 @@ impl Writer for L { impl OneDimensionalCodeWriter for L { fn encode_oned(&self, _contents: &str) -> Result> { - todo!() + unimplemented!() } } diff --git a/src/oned/upc_ean_reader.rs b/src/oned/upc_ean_reader.rs index caa984a..11b1273 100644 --- a/src/oned/upc_ean_reader.rs +++ b/src/oned/upc_ean_reader.rs @@ -490,7 +490,7 @@ pub trait UPCEANReader: OneDReader { pub(crate) struct StandInStruct; impl UPCEANReader for StandInStruct { fn getBarcodeFormat(&self) -> BarcodeFormat { - todo!() + unimplemented!() } fn decodeMiddle( @@ -499,7 +499,7 @@ impl UPCEANReader for StandInStruct { _startRange: &[usize; 2], _resultString: &mut String, ) -> Result { - todo!() + unimplemented!() } } impl OneDReader for StandInStruct { @@ -509,13 +509,13 @@ impl OneDReader for StandInStruct { _row: &BitArray, _hints: &crate::DecodingHintDictionary, ) -> Result { - todo!() + unimplemented!() } } impl Reader for StandInStruct { fn decode(&mut self, _image: &mut crate::BinaryBitmap) -> Result { - todo!() + unimplemented!() } fn decode_with_hints( @@ -523,7 +523,7 @@ impl Reader for StandInStruct { _image: &mut crate::BinaryBitmap, _hints: &crate::DecodingHintDictionary, ) -> Result { - todo!() + unimplemented!() } } From c2ca7fa2b05f9248db6da54bf6ab817b142b7b84 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 21 Mar 2023 11:59:56 -0500 Subject: [PATCH 21/72] port find bounding box though it might not need to be ported, evaluate removing later. --- .../base_extentions/bitmatrix.rs | 89 +++++++++++-------- 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/src/common/cpp_essentials/base_extentions/bitmatrix.rs b/src/common/cpp_essentials/base_extentions/bitmatrix.rs index 174eef2..df9f399 100644 --- a/src/common/cpp_essentials/base_extentions/bitmatrix.rs +++ b/src/common/cpp_essentials/base_extentions/bitmatrix.rs @@ -1,6 +1,7 @@ use crate::common::BitMatrix; use crate::common::Result; use crate::point; +use crate::Point; impl BitMatrix { pub fn Deflate( @@ -28,26 +29,23 @@ impl BitMatrix { } pub fn getTopLeftOnBitWithPosition(&self, left: &mut u32, top: &mut u32) -> bool { - todo!() - // int bitsOffset = (int)std::distance(_bits.begin(), std::find_if(_bits.begin(), _bits.end(), isSet)); - // if (bitsOffset == Size(_bits)) { - // return false; - // } - // top = bitsOffset / _width; - // left = (bitsOffset % _width); - // return true; + let Some(Point{x,y}) = self.getTopLeftOnBit() else { + return false; + }; + *left = x as u32; + *top = y as u32; + + true } pub fn getBottomRightOnBitWithPosition(&self, right: &mut u32, bottom: &mut u32) -> bool { - todo!() - // int bitsOffset = Size(_bits) - 1 - (int)std::distance(_bits.rbegin(), std::find_if(_bits.rbegin(), _bits.rend(), isSet)); - // if (bitsOffset < 0) { - // return false; - // } + let Some(Point{x,y}) = self.getBottomRightOnBit() else { + return false; + }; + *right = x as u32; + *bottom = y as u32; - // bottom = bitsOffset / _width; - // right = (bitsOffset % _width); - // return true; + true } pub fn findBoundingBox( @@ -58,26 +56,47 @@ impl BitMatrix { height: u32, minSize: u32, ) -> (bool, u32, u32, u32, u32) { - todo!() - // int right, bottom; - // if (!getTopLeftOnBit(left, top) || !getBottomRightOnBit(right, bottom) || bottom - top + 1 < minSize) - // return false; + let mut left = left; + let mut top = top; + let mut width = width; + let mut height = height; - // for (int y = top; y <= bottom; y++ ) { - // for (int x = 0; x < left; ++x) - // if (get(x, y)) { - // left = x; - // break; - // } - // for (int x = _width-1; x > right; x--) - // if (get(x, y)) { - // right = x; - // break; - // } - // } + let mut right = 0; + let mut bottom = 0; + if (!self.getTopLeftOnBitWithPosition(&mut left, &mut top) + || !self.getBottomRightOnBitWithPosition(&mut right, &mut bottom) + || bottom - top + 1 < minSize) + { + return (false, left, top, width, height); + } - // width = right - left + 1; - // height = bottom - top + 1; - // return width >= minSize && height >= minSize; + for y in top..=bottom { + // for (int y = top; y <= bottom; y++ ) { + for x in 0..left { + // for (int x = 0; x < left; ++x){ + if (self.get(x, y)) { + left = x; + break; + } + } + for x in (right..(self.width() - 1)).rev() { + // for (int x = _width-1; x > right; x--){ + if (self.get(x, y)) { + right = x; + break; + } + } + } + + width = right - left + 1; + height = bottom - top + 1; + + ( + width >= minSize && height >= minSize, + left, + top, + width, + height, + ) } } From d2316408b502cac256204a7a7497a938c0db4ea7 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 21 Mar 2023 15:26:45 -0500 Subject: [PATCH 22/72] stage for port --- src/qrcode/cpp_port/decoder.rs | 360 +++++++++++++++++++++++++++++ src/qrcode/cpp_port/decoder/mod.rs | 1 - 2 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 src/qrcode/cpp_port/decoder.rs delete mode 100644 src/qrcode/cpp_port/decoder/mod.rs diff --git a/src/qrcode/cpp_port/decoder.rs b/src/qrcode/cpp_port/decoder.rs new file mode 100644 index 0000000..3772591 --- /dev/null +++ b/src/qrcode/cpp_port/decoder.rs @@ -0,0 +1,360 @@ +// /* +// * Copyright 2016 Nu-book Inc. +// * Copyright 2016 ZXing authors +// */ +// // SPDX-License-Identifier: Apache-2.0 + +// #include "QRDecoder.h" + +// #include "BitMatrix.h" +// #include "BitSource.h" +// #include "CharacterSet.h" +// #include "DecoderResult.h" +// #include "GenericGF.h" +// #include "QRBitMatrixParser.h" +// #include "QRCodecMode.h" +// #include "QRDataBlock.h" +// #include "QRFormatInformation.h" +// #include "QRVersion.h" +// #include "ReedSolomonDecoder.h" +// #include "StructuredAppend.h" +// #include "ZXAlgorithms.h" +// #include "ZXTestSupport.h" + +// #include +// #include +// #include +// #include + +// namespace ZXing::QRCode { + +// /** +// *

Given data and error-correction codewords received, possibly corrupted by errors, attempts to +// * correct the errors in-place using Reed-Solomon error correction.

+// * +// * @param codewordBytes data and error correction codewords +// * @param numDataCodewords number of codewords that are data bytes +// * @return false if error correction fails +// */ +// static bool CorrectErrors(ByteArray& codewordBytes, int numDataCodewords) +// { +// // First read into an array of ints +// std::vector codewordsInts(codewordBytes.begin(), codewordBytes.end()); + +// int numECCodewords = Size(codewordBytes) - numDataCodewords; +// if (!ReedSolomonDecode(GenericGF::QRCodeField256(), codewordsInts, numECCodewords)) +// return false; + +// // Copy back into array of bytes -- only need to worry about the bytes that were data +// // We don't care about errors in the error-correction codewords +// std::copy_n(codewordsInts.begin(), numDataCodewords, codewordBytes.begin()); +// return true; +// } + + +// /** +// * See specification GBT 18284-2000 +// */ +// static void DecodeHanziSegment(BitSource& bits, int count, Content& result) +// { +// // Each character will require 2 bytes, decode as GB2312 +// // There is no ECI value for GB2312, use GB18030 which is a superset +// result.switchEncoding(CharacterSet::GB18030); +// result.reserve(2 * count); + +// while (count > 0) { +// // Each 13 bits encodes a 2-byte character +// int twoBytes = bits.readBits(13); +// int assembledTwoBytes = ((twoBytes / 0x060) << 8) | (twoBytes % 0x060); +// if (assembledTwoBytes < 0x00A00) { +// // In the 0xA1A1 to 0xAAFE range +// assembledTwoBytes += 0x0A1A1; +// } else { +// // In the 0xB0A1 to 0xFAFE range +// assembledTwoBytes += 0x0A6A1; +// } +// result += narrow_cast((assembledTwoBytes >> 8) & 0xFF); +// result += narrow_cast(assembledTwoBytes & 0xFF); +// count--; +// } +// } + +// static void DecodeKanjiSegment(BitSource& bits, int count, Content& result) +// { +// // Each character will require 2 bytes. Read the characters as 2-byte pairs +// // and decode as Shift_JIS afterwards +// result.switchEncoding(CharacterSet::Shift_JIS); +// result.reserve(2 * count); + +// while (count > 0) { +// // Each 13 bits encodes a 2-byte character +// int twoBytes = bits.readBits(13); +// int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0); +// if (assembledTwoBytes < 0x01F00) { +// // In the 0x8140 to 0x9FFC range +// assembledTwoBytes += 0x08140; +// } else { +// // In the 0xE040 to 0xEBBF range +// assembledTwoBytes += 0x0C140; +// } +// result += narrow_cast(assembledTwoBytes >> 8); +// result += narrow_cast(assembledTwoBytes); +// count--; +// } +// } + +// static void DecodeByteSegment(BitSource& bits, int count, Content& result) +// { +// result.switchEncoding(CharacterSet::Unknown); +// result.reserve(count); + +// for (int i = 0; i < count; i++) +// result += narrow_cast(bits.readBits(8)); +// } + +// static char ToAlphaNumericChar(int value) +// { +// /** +// * See ISO 18004:2006, 6.4.4 Table 5 +// */ +// static const char ALPHANUMERIC_CHARS[] = { +// '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', +// 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', +// 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', +// ' ', '$', '%', '*', '+', '-', '.', '/', ':' +// }; + +// if (value < 0 || value >= Size(ALPHANUMERIC_CHARS)) +// throw std::out_of_range("ToAlphaNumericChar: out of range"); + +// return ALPHANUMERIC_CHARS[value]; +// } + +// static void DecodeAlphanumericSegment(BitSource& bits, int count, Content& result) +// { +// // Read two characters at a time +// std::string buffer; +// while (count > 1) { +// int nextTwoCharsBits = bits.readBits(11); +// buffer += ToAlphaNumericChar(nextTwoCharsBits / 45); +// buffer += ToAlphaNumericChar(nextTwoCharsBits % 45); +// count -= 2; +// } +// if (count == 1) { +// // special case: one character left +// buffer += ToAlphaNumericChar(bits.readBits(6)); +// } +// // See section 6.4.8.1, 6.4.8.2 +// if (result.symbology.aiFlag != AIFlag::None) { +// // We need to massage the result a bit if in an FNC1 mode: +// for (size_t i = 0; i < buffer.length(); i++) { +// if (buffer[i] == '%') { +// if (i < buffer.length() - 1 && buffer[i + 1] == '%') { +// // %% is rendered as % +// buffer.erase(i + 1); +// } else { +// // In alpha mode, % should be converted to FNC1 separator 0x1D +// buffer[i] = static_cast(0x1D); +// } +// } +// } +// } + +// result.switchEncoding(CharacterSet::ISO8859_1); +// result += buffer; +// } + +// static void DecodeNumericSegment(BitSource& bits, int count, Content& result) +// { +// result.switchEncoding(CharacterSet::ISO8859_1); +// result.reserve(count); + +// while (count) { +// int n = std::min(count, 3); +// int nDigits = bits.readBits(1 + 3 * n); // read 4, 7 or 10 bits into 1, 2 or 3 digits +// result.append(ZXing::ToString(nDigits, n)); +// count -= n; +// } +// } + +// static ECI ParseECIValue(BitSource& bits) +// { +// int firstByte = bits.readBits(8); +// if ((firstByte & 0x80) == 0) { +// // just one byte +// return ECI(firstByte & 0x7F); +// } +// if ((firstByte & 0xC0) == 0x80) { +// // two bytes +// int secondByte = bits.readBits(8); +// return ECI(((firstByte & 0x3F) << 8) | secondByte); +// } +// if ((firstByte & 0xE0) == 0xC0) { +// // three bytes +// int secondThirdBytes = bits.readBits(16); +// return ECI(((firstByte & 0x1F) << 16) | secondThirdBytes); +// } +// throw FormatError("ParseECIValue: invalid value"); +// } + +// /** +// * QR codes encode mode indicators and terminator codes into a constant bit length of 4. +// * Micro QR codes have terminator codes that vary in bit length but are always longer than +// * the mode indicators. +// * M1 - 0 length mode code, 3 bits terminator code +// * M2 - 1 bit mode code, 5 bits terminator code +// * M3 - 2 bit mode code, 7 bits terminator code +// * M4 - 3 bit mode code, 9 bits terminator code +// * IsTerminator peaks into the bit stream to see if the current position is at the start of +// * a terminator code. If true, then the decoding can finish. If false, then the decoding +// * can read off the next mode code. +// * +// * See ISO 18004:2015, 7.4.1 Table 2 +// * +// * @param bits the stream of bits that might have a terminator code +// * @param version the QR or micro QR code version +// */ +// bool IsEndOfStream(const BitSource& bits, const Version& version) +// { +// const int bitsRequired = TerminatorBitsLength(version); +// const int bitsAvailable = std::min(bits.available(), bitsRequired); +// return bitsAvailable == 0 || bits.peakBits(bitsAvailable) == 0; +// } + +// /** +// *

QR Codes can encode text as bits in one of several modes, and can use multiple modes +// * in one QR Code. This method decodes the bits back into text.

+// * +// *

See ISO 18004:2006, 6.4.3 - 6.4.7

+// */ +// ZXING_EXPORT_TEST_ONLY +// DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel ecLevel) +// { +// BitSource bits(bytes); +// Content result; +// Error error; +// result.symbology = {'Q', '1', 1}; +// StructuredAppendInfo structuredAppend; +// const int modeBitLength = CodecModeBitsLength(version); + +// try +// { +// while(!IsEndOfStream(bits, version)) { +// CodecMode mode; +// if (modeBitLength == 0) +// mode = CodecMode::NUMERIC; // MicroQRCode version 1 is always NUMERIC and modeBitLength is 0 +// else +// mode = CodecModeForBits(bits.readBits(modeBitLength), version.isMicroQRCode()); + +// switch (mode) { +// case CodecMode::FNC1_FIRST_POSITION: +// // if (!result.empty()) // uncomment to enforce specification +// // throw FormatError("GS1 Indicator (FNC1 in first position) at illegal position"); +// result.symbology.modifier = '3'; +// result.symbology.aiFlag = AIFlag::GS1; // In Alphanumeric mode undouble doubled '%' and treat single '%' as +// break; +// case CodecMode::FNC1_SECOND_POSITION: +// if (!result.empty()) +// throw FormatError("AIM Application Indicator (FNC1 in second position) at illegal position"); +// result.symbology.modifier = '5'; // As above +// // ISO/IEC 18004:2015 7.4.8.3 AIM Application Indicator (FNC1 in second position), "00-99" or "A-Za-z" +// if (int appInd = bits.readBits(8); appInd < 100) // "00-09" +// result += ZXing::ToString(appInd, 2); +// else if ((appInd >= 165 && appInd <= 190) || (appInd >= 197 && appInd <= 222)) // "A-Za-z" +// result += narrow_cast(appInd - 100); +// else +// throw FormatError("Invalid AIM Application Indicator"); +// result.symbology.aiFlag = AIFlag::AIM; // see also above +// break; +// case CodecMode::STRUCTURED_APPEND: +// // sequence number and parity is added later to the result metadata +// // Read next 4 bits of index, 4 bits of symbol count, and 8 bits of parity data, then continue +// structuredAppend.index = bits.readBits(4); +// structuredAppend.count = bits.readBits(4) + 1; +// structuredAppend.id = std::to_string(bits.readBits(8)); +// break; +// case CodecMode::ECI: +// // Count doesn't apply to ECI +// result.switchEncoding(ParseECIValue(bits)); +// break; +// case CodecMode::HANZI: { +// // First handle Hanzi mode which does not start with character count +// // chinese mode contains a sub set indicator right after mode indicator +// if (int subset = bits.readBits(4); subset != 1) // GB2312_SUBSET is the only supported one right now +// throw FormatError("Unsupported HANZI subset"); +// int count = bits.readBits(CharacterCountBits(mode, version)); +// DecodeHanziSegment(bits, count, result); +// break; +// } +// default: { +// // "Normal" QR code modes: +// // How many characters will follow, encoded in this mode? +// int count = bits.readBits(CharacterCountBits(mode, version)); +// switch (mode) { +// case CodecMode::NUMERIC: DecodeNumericSegment(bits, count, result); break; +// case CodecMode::ALPHANUMERIC: DecodeAlphanumericSegment(bits, count, result); break; +// case CodecMode::BYTE: DecodeByteSegment(bits, count, result); break; +// case CodecMode::KANJI: DecodeKanjiSegment(bits, count, result); break; +// default: throw FormatError("Invalid CodecMode"); +// } +// break; +// } +// } +// } +// } catch (std::out_of_range& e) { // see BitSource::readBits +// error = FormatError("Truncated bit stream"); +// } catch (Error e) { +// error = std::move(e); +// } + +// return DecoderResult(std::move(result)) +// .setError(std::move(error)) +// .setEcLevel(ToString(ecLevel)) +// .setVersionNumber(version.versionNumber()) +// .setStructuredAppend(structuredAppend); +// } + +// DecoderResult Decode(const BitMatrix& bits) +// { +// const Version* pversion = ReadVersion(bits); +// if (!pversion) +// return FormatError("Invalid version"); +// const Version& version = *pversion; + +// auto formatInfo = ReadFormatInformation(bits, version.isMicroQRCode()); +// if (!formatInfo.isValid()) +// return FormatError("Invalid format information"); + +// // Read codewords +// ByteArray codewords = ReadCodewords(bits, version, formatInfo); +// if (codewords.empty()) +// return FormatError("Failed to read codewords"); + +// // Separate into data blocks +// std::vector dataBlocks = DataBlock::GetDataBlocks(codewords, version, formatInfo.ecLevel); +// if (dataBlocks.empty()) +// return FormatError("Failed to get data blocks"); + +// // Count total number of data bytes +// const auto op = [](auto totalBytes, const auto& dataBlock){ return totalBytes + dataBlock.numDataCodewords();}; +// const auto totalBytes = std::accumulate(std::begin(dataBlocks), std::end(dataBlocks), int{}, op); +// ByteArray resultBytes(totalBytes); +// auto resultIterator = resultBytes.begin(); + +// // Error-correct and copy data blocks together into a stream of bytes +// for (auto& dataBlock : dataBlocks) +// { +// ByteArray& codewordBytes = dataBlock.codewords(); +// int numDataCodewords = dataBlock.numDataCodewords(); + +// if (!CorrectErrors(codewordBytes, numDataCodewords)) +// return ChecksumError(); + +// resultIterator = std::copy_n(codewordBytes.begin(), numDataCodewords, resultIterator); +// } + +// // Decode the contents of that stream of bytes +// return DecodeBitStream(std::move(resultBytes), version, formatInfo.ecLevel).setIsMirrored(formatInfo.isMirrored); +// } + +// } // namespace ZXing::QRCode diff --git a/src/qrcode/cpp_port/decoder/mod.rs b/src/qrcode/cpp_port/decoder/mod.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/qrcode/cpp_port/decoder/mod.rs +++ /dev/null @@ -1 +0,0 @@ - From 0636a632915c1925997fb7cb595678ebdcc16216 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 27 Mar 2023 11:45:26 -0500 Subject: [PATCH 23/72] partial stub out of decoder --- src/common/bit_source.rs | 4 + .../base_extentions/qrcode_version.rs | 4 + src/common/cpp_essentials/decoder_result.rs | 200 +++++ src/common/cpp_essentials/mod.rs | 4 + .../cpp_essentials/structured_append.rs | 16 + src/common/cpp_essentials/util.rs | 26 + src/common/eci_string_builder.rs | 68 +- src/qrcode/cpp_port/bitmatrix_parser.rs | 203 +++++ src/qrcode/cpp_port/decoder.rs | 725 ++++++++++-------- src/qrcode/cpp_port/mod.rs | 2 + src/qrcode/decoder/error_correction_level.rs | 13 + src/qrcode/decoder/format_information.rs | 3 + src/qrcode/decoder/mode.rs | 26 + 13 files changed, 977 insertions(+), 317 deletions(-) create mode 100644 src/common/cpp_essentials/decoder_result.rs create mode 100644 src/common/cpp_essentials/structured_append.rs create mode 100644 src/qrcode/cpp_port/bitmatrix_parser.rs diff --git a/src/common/bit_source.rs b/src/common/bit_source.rs index 7e10f4e..16cba87 100644 --- a/src/common/bit_source.rs +++ b/src/common/bit_source.rs @@ -116,6 +116,10 @@ impl BitSource { Ok(result) } + pub fn peak_bits(&self, numBits: usize) -> Result { + todo!() + } + /** * @return number of bits that can be read successfully */ diff --git a/src/common/cpp_essentials/base_extentions/qrcode_version.rs b/src/common/cpp_essentials/base_extentions/qrcode_version.rs index a63a2aa..49cad90 100644 --- a/src/common/cpp_essentials/base_extentions/qrcode_version.rs +++ b/src/common/cpp_essentials/base_extentions/qrcode_version.rs @@ -65,4 +65,8 @@ impl Version { // If we didn't find a close enough match, fail return Err(Exceptions::ILLEGAL_STATE); } + + pub fn isMicroQRCode(&self) -> bool { + todo!() + } } diff --git a/src/common/cpp_essentials/decoder_result.rs b/src/common/cpp_essentials/decoder_result.rs new file mode 100644 index 0000000..0906e13 --- /dev/null +++ b/src/common/cpp_essentials/decoder_result.rs @@ -0,0 +1,200 @@ +use std::{any::Any, rc::Rc}; + +use crate::common::ECIStringBuilder; + +use super::StructuredAppendInfo; + +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct DecoderResult +where + T: Copy + Clone + Default + Eq + PartialEq, +{ + content: ECIStringBuilder, + ecLevel: String, + lineCount: u32, // = 0; + versionNumber: u32, // = 0; + structuredAppend: StructuredAppendInfo, + isMirrored: bool, // = false; + readerInit: bool, // = false; + //Error _error; + //std::shared_ptr _extra; + extra: Rc, +} + +impl Default for DecoderResult +where + T: Copy + Clone + Default + Eq + PartialEq, +{ + fn default() -> Self { + Self { + content: Default::default(), + ecLevel: Default::default(), + lineCount: 0, + versionNumber: 0, + structuredAppend: Default::default(), + isMirrored: false, + readerInit: false, + extra: Default::default(), + } + } +} + +impl DecoderResult +where + T: Copy + Clone + Default + Eq + PartialEq, +{ + pub fn new() -> Self { + Self::default() + } + pub fn with_eci_string_builder(src: ECIStringBuilder) -> Self { + todo!() + } + + pub fn isValid(&self) -> bool { + //return includeErrors || (_content.symbology.code != 0 && !_error); + todo!() + } + + pub fn content(&self) -> &ECIStringBuilder { + &self.content + } +} + +// DecoderResult(const DecoderResult &) = delete; +// DecoderResult& operator=(const DecoderResult &) = delete; +// } + +// public: +// DecoderResult() = default; +// DecoderResult(Error error) : _error(std::move(error)) {} +// DecoderResult(Content&& bytes) : _content(std::move(bytes)) {} + +// DecoderResult(DecoderResult&&) noexcept = default; +// DecoderResult& operator=(DecoderResult&&) noexcept = default; + +// bool isValid(bool includeErrors = false) const +// { +// return includeErrors || (_content.symbology.code != 0 && !_error); +// } + +// const Content& content() const & { return _content; } +// Content&& content() && { return std::move(_content); } + +// to keep the unit tests happy for now: +// std::wstring text() const { return _content.utfW(); } +// std::string symbologyIdentifier() const { return _content.symbology.toString(false); } + +// Simple macro to set up getter/setter methods that save lots of boilerplate. +// It sets up a standard 'const & () const', 2 setters for setting lvalues via +// copy and 2 for setting rvalues via move. They are provided each to work +// either on lvalues (normal 'void (...)') or on rvalues (returning '*this' as +// rvalue). The latter can be used to optionally initialize a temporary in a +// return statement, e.g. +// return DecoderResult(bytes, text).setEcLevel(level); +// #define ZX_PROPERTY(TYPE, GETTER, SETTER) \ +// const TYPE& GETTER() const & { return _##GETTER; } \ +// TYPE&& GETTER() && { return std::move(_##GETTER); } \ +// void SETTER(const TYPE& v) & { _##GETTER = v; } \ +// void SETTER(TYPE&& v) & { _##GETTER = std::move(v); } \ +// DecoderResult&& SETTER(const TYPE& v) && { _##GETTER = v; return std::move(*this); } \ +// DecoderResult&& SETTER(TYPE&& v) && { _##GETTER = std::move(v); return std::move(*this); } + +// ZX_PROPERTY(std::string, ecLevel, setEcLevel) +// ZX_PROPERTY(int, lineCount, setLineCount) +// ZX_PROPERTY(int, versionNumber, setVersionNumber) +// ZX_PROPERTY(StructuredAppendInfo, structuredAppend, setStructuredAppend) + +// ZX_PROPERTY(Error, error, setError) + +// ZX_PROPERTY(bool, isMirrored, setIsMirrored) +// ZX_PROPERTY(bool, readerInit, setReaderInit) +// ZX_PROPERTY(std::shared_ptr, extra, setExtra) + +// #undef ZX_PROPERTY +// }; + +impl DecoderResult +where + T: Copy + Clone + Default + Eq + PartialEq, +{ + pub fn ecLevel(&self) -> &str { + &self.ecLevel + } + pub fn setEcLevel(&mut self, ecLevel: String) { + self.ecLevel = ecLevel + } + pub fn withEcLevel(mut self, ecLevel: String) -> DecoderResult { + self.setEcLevel(ecLevel); + self + } + + pub fn lineCount(&self) -> u32 { + self.lineCount + } + pub fn setLineCount(&mut self, lc: u32) { + self.lineCount = lc + } + pub fn withLineCount(mut self, lc: u32) -> DecoderResult { + self.setLineCount(lc); + self + } + + pub fn versionNumber(&self) -> u32 { + self.versionNumber + } + pub fn setVersionNumber(&mut self, vn: u32) { + self.versionNumber = vn + } + pub fn withVersionNumber(mut self, vn: u32) -> DecoderResult { + self.setVersionNumber(vn); + self + } + + pub fn structuredAppend(&self) -> &StructuredAppendInfo { + &self.structuredAppend + } + pub fn setStructuredAppend(&mut self, sai: StructuredAppendInfo) { + self.structuredAppend = sai + } + pub fn withStructuredAppend(mut self, sai: StructuredAppendInfo) -> DecoderResult { + self.setStructuredAppend(sai); + self + } + + pub fn isMirrored(&self) -> bool { + self.isMirrored + } + pub fn setIsMirrored(&mut self, is_mirrored: bool) { + self.isMirrored = is_mirrored + } + pub fn withIsMirrored(mut self, is_mirrored: bool) -> DecoderResult { + self.setIsMirrored(is_mirrored); + self + } + + pub fn readerInit(&self) -> bool { + self.readerInit + } + pub fn setReaderInit(&mut self, reader_init: bool) { + self.readerInit = reader_init + } + pub fn withReaderInit(mut self, reader_init: bool) -> DecoderResult { + self.setReaderInit(reader_init); + self + } + + pub fn extra(&self) -> Rc { + self.extra.clone() + } + pub fn setExtra(&mut self, extra: Rc) { + self.extra = extra + } + pub fn withExtra(mut self, extra: Rc) -> DecoderResult { + self.setExtra(extra); + self + } + + // pub fn build(self) -> DecoderResult { + + // } +} diff --git a/src/common/cpp_essentials/mod.rs b/src/common/cpp_essentials/mod.rs index 817e38b..07120a8 100644 --- a/src/common/cpp_essentials/mod.rs +++ b/src/common/cpp_essentials/mod.rs @@ -3,6 +3,7 @@ mod base_extentions; pub mod bitmatrix_cursor; pub mod bitmatrix_cursor_trait; pub mod concentric_finder; +pub mod decoder_result; pub mod direction; pub mod dm_regression_line; pub mod edge_tracer; @@ -12,12 +13,14 @@ pub mod pattern; pub mod regression_line; pub mod regression_line_trait; pub mod step_result; +pub mod structured_append; pub mod util; pub mod value; pub use bitmatrix_cursor::*; pub use bitmatrix_cursor_trait::*; pub use concentric_finder::*; +pub use decoder_result::*; pub use direction::*; pub use dm_regression_line::*; pub use edge_tracer::*; @@ -27,5 +30,6 @@ pub use pattern::*; pub use regression_line::*; pub use regression_line_trait::*; pub use step_result::*; +pub use structured_append::*; pub use util::*; pub use value::*; diff --git a/src/common/cpp_essentials/structured_append.rs b/src/common/cpp_essentials/structured_append.rs new file mode 100644 index 0000000..d992e03 --- /dev/null +++ b/src/common/cpp_essentials/structured_append.rs @@ -0,0 +1,16 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StructuredAppendInfo { + pub index: i32, // -1; + pub count: i32, // = -1; + pub id: String, +} + +impl Default for StructuredAppendInfo { + fn default() -> Self { + Self { + index: -1, + count: -1, + id: Default::default(), + } + } +} diff --git a/src/common/cpp_essentials/util.rs b/src/common/cpp_essentials/util.rs index d98b866..3947164 100644 --- a/src/common/cpp_essentials/util.rs +++ b/src/common/cpp_essentials/util.rs @@ -38,3 +38,29 @@ pub fn UpdateMinMaxFloat(min: &mut f64, max: &mut f64, val: f64) { *min = f64::min(*min, val); *max = f64::max(*max, val); } + +// template>> +pub fn ToString>(val: T, len: usize) -> Result { + let mut len = len; + let mut val = val.into(); + + let mut result: String = vec!['0'; len].iter().collect(); + len -= 1; + // std::string result(len--, '0'); + if (val < 0) { + return Err(Exceptions::format_with("Invalid value")); + } + while len >= 0 && val != 0 { + result.replace_range(len..len, &char::from(b'0' + (val % 10) as u8).to_string()); + + len -= 1; + val /= 10; + } + // for (; len >= 0 && val != 0; --len, val /= 10) { + // result[len] = '0' + val % 10;} + if (val != 0) { + return Err(Exceptions::format_with("Invalid value")); + } + + Ok(result) +} diff --git a/src/common/eci_string_builder.rs b/src/common/eci_string_builder.rs index a43dd6a..72bfa6a 100644 --- a/src/common/eci_string_builder.rs +++ b/src/common/eci_string_builder.rs @@ -30,12 +30,13 @@ use super::{CharacterSet, Eci}; * * @author Alex Geller */ -#[derive(Default)] +#[derive(Default, PartialEq, Eq, Debug, Clone)] pub struct ECIStringBuilder { is_eci: bool, eci_result: Option, bytes: Vec, eci_positions: Vec<(Eci, usize, usize)>, // (Eci, start, end) + pub symbology: SymbologyIdentifier, } impl ECIStringBuilder { @@ -45,6 +46,7 @@ impl ECIStringBuilder { bytes: Vec::with_capacity(initial_capacity), eci_positions: Vec::default(), is_eci: false, + symbology: SymbologyIdentifier::default(), } } @@ -120,6 +122,11 @@ impl ECIStringBuilder { } } + /// Change the current encoding characterset, finding an eci to do so + pub fn switch_encoding(&mut self, charset: CharacterSet) { + self.append_eci(Eci::from(charset)) + } + /// Finishes encoding anything in the buffer using the current ECI and resets. /// /// This function can panic @@ -198,6 +205,11 @@ impl ECIStringBuilder { self.bytes.len() } + /// Reserve an additional number of bytes for storage + pub fn reserve(&mut self, additional: usize) { + self.bytes.reserve(additional); + } + /** * @return true iff nothing has been appended */ @@ -221,3 +233,57 @@ impl fmt::Display for ECIStringBuilder { } } } + +impl std::ops::AddAssign for ECIStringBuilder { + fn add_assign(&mut self, rhs: u8) { + self.append_byte(rhs) + } +} + +impl std::ops::AddAssign for ECIStringBuilder { + fn add_assign(&mut self, rhs: String) { + self.append_string(&rhs) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ContentType { + Text, + Binary, + Mixed, + GS1, + ISO15434, + UnknownECI, +} +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum AIFlag { + None, + GS1, + AIM, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct SymbologyIdentifier { + //char code = 0, modifier = 0, eciModifierOffset = 0; + pub code: u8, + pub modifier: u8, + pub eciModifierOffset: u8, + pub aiFlag: AIFlag, + // AIFlag aiFlag = AIFlag::None; + + // std::string toString(bool hasECI = false) const + // { + // return code ? ']' + std::string(1, code) + static_cast(modifier + eciModifierOffset * hasECI) : std::string(); + // } +} + +impl Default for SymbologyIdentifier { + fn default() -> Self { + Self { + code: 0, + modifier: 0, + eciModifierOffset: 0, + aiFlag: AIFlag::None, + } + } +} diff --git a/src/qrcode/cpp_port/bitmatrix_parser.rs b/src/qrcode/cpp_port/bitmatrix_parser.rs new file mode 100644 index 0000000..40a98e8 --- /dev/null +++ b/src/qrcode/cpp_port/bitmatrix_parser.rs @@ -0,0 +1,203 @@ +// /* +// * Copyright 2016 Nu-book Inc. +// * Copyright 2016 ZXing authors +// */ +// // SPDX-License-Identifier: Apache-2.0 + +use crate::{ + common::{BitMatrix, Result}, + qrcode::decoder::{FormatInformation, Version, VersionRef}, + Exceptions, +}; + +pub fn getBit(bitMatrix: &BitMatrix, x: u32, y: u32, mirrored: Option) -> bool { + let mirrored = mirrored.unwrap_or(false); + if mirrored { + bitMatrix.get(y, x) + } else { + bitMatrix.get(x, y) + } +} + +pub fn hasValidDimension(bitMatrix: &BitMatrix, isMicro: bool) -> bool { + let dimension = bitMatrix.height(); + if (isMicro) { + dimension >= 11 && dimension <= 17 && (dimension % 2) == 1 + } else { + dimension >= 21 && dimension <= 177 && (dimension % 4) == 1 + } +} + +pub fn ReadVersion(bitMatrix: &BitMatrix) -> Result { + todo!() + // int dimension = bitMatrix.height(); + + // const Version* version = Version::FromDimension(dimension); + + // if (!version || version->versionNumber() < 7) + // return version; + + // for (bool mirror : {false, true}) { + // // Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored) + // int versionBits = 0; + // for (int y = 5; y >= 0; --y) + // for (int x = dimension - 9; x >= dimension - 11; --x) + // AppendBit(versionBits, getBit(bitMatrix, x, y, mirror)); + + // version = Version::DecodeVersionInformation(versionBits); + // if (version && version->dimension() == dimension) + // return version; + // } + + // return nullptr; +} + +pub fn ReadFormatInformation(bitMatrix: &BitMatrix, isMicro: bool) -> Result { + todo!() + // if (!hasValidDimension(bitMatrix, isMicro)) + // return {}; + + // if (isMicro) { + // // Read top-left format info bits + // int formatInfoBits = 0; + // for (int x = 1; x < 9; x++) + // AppendBit(formatInfoBits, getBit(bitMatrix, x, 8)); + // for (int y = 7; y >= 1; y--) + // AppendBit(formatInfoBits, getBit(bitMatrix, 8, y)); + + // return FormatInformation::DecodeMQR(formatInfoBits); + // } + + // // Read top-left format info bits + // int formatInfoBits1 = 0; + // for (int x = 0; x < 6; x++) + // AppendBit(formatInfoBits1, getBit(bitMatrix, x, 8)); + // // .. and skip a bit in the timing pattern ... + // AppendBit(formatInfoBits1, getBit(bitMatrix, 7, 8)); + // AppendBit(formatInfoBits1, getBit(bitMatrix, 8, 8)); + // AppendBit(formatInfoBits1, getBit(bitMatrix, 8, 7)); + // // .. and skip a bit in the timing pattern ... + // for (int y = 5; y >= 0; y--) + // AppendBit(formatInfoBits1, getBit(bitMatrix, 8, y)); + + // // Read the top-right/bottom-left pattern including the 'Dark Module' from the bottom-left + // // part that has to be considered separately when looking for mirrored symbols. + // // See also FormatInformation::DecodeQR + // int dimension = bitMatrix.height(); + // int formatInfoBits2 = 0; + // for (int y = dimension - 1; y >= dimension - 8; y--) + // AppendBit(formatInfoBits2, getBit(bitMatrix, 8, y)); + // for (int x = dimension - 8; x < dimension; x++) + // AppendBit(formatInfoBits2, getBit(bitMatrix, x, 8)); + + // return FormatInformation::DecodeQR(formatInfoBits1, formatInfoBits2); +} + +pub fn ReadQRCodewords( + bitMatrix: &BitMatrix, + version: VersionRef, + formatInfo: &FormatInformation, +) -> Result> { + todo!() + // BitMatrix functionPattern = version.buildFunctionPattern(); + + // ByteArray result; + // result.reserve(version.totalCodewords()); + // uint8_t currentByte = 0; + // bool readingUp = true; + // int bitsRead = 0; + // int dimension = bitMatrix.height(); + // // Read columns in pairs, from right to left + // for (int x = dimension - 1; x > 0; x -= 2) { + // // Skip whole column with vertical timing pattern. + // if (x == 6) + // x--; + // // Read alternatingly from bottom to top then top to bottom + // for (int row = 0; row < dimension; row++) { + // int y = readingUp ? dimension - 1 - row : row; + // for (int col = 0; col < 2; col++) { + // int xx = x - col; + // // Ignore bits covered by the function pattern + // if (!functionPattern.get(xx, y)) { + // // Read a bit + // AppendBit(currentByte, + // GetDataMaskBit(formatInfo.dataMask, xx, y) != getBit(bitMatrix, xx, y, formatInfo.isMirrored)); + // // If we've made a whole byte, save it off + // if (++bitsRead % 8 == 0) + // result.push_back(std::exchange(currentByte, 0)); + // } + // } + // } + // readingUp = !readingUp; // switch directions + // } + // if (Size(result) != version.totalCodewords()) + // return {}; + + // return result; +} + +pub fn ReadMQRCodewords( + bitMatrix: &BitMatrix, + version: VersionRef, + formatInfo: &FormatInformation, +) -> Result> { + todo!() + // BitMatrix functionPattern = version.buildFunctionPattern(); + + // // D3 in a Version M1 symbol, D11 in a Version M3-L symbol and D9 + // // in a Version M3-M symbol is a 2x2 square 4-module block. + // // See ISO 18004:2006 6.7.3. + // bool hasD4mBlock = version.versionNumber() % 2 == 1; + // int d4mBlockIndex = + // version.versionNumber() == 1 ? 3 : (formatInfo.ecLevel == QRCode::ErrorCorrectionLevel::Low ? 11 : 9); + + // ByteArray result; + // result.reserve(version.totalCodewords()); + // uint8_t currentByte = 0; + // bool readingUp = true; + // int bitsRead = 0; + // int dimension = bitMatrix.height(); + // // Read columns in pairs, from right to left + // for (int x = dimension - 1; x > 0; x -= 2) { + // // Read alternatingly from bottom to top then top to bottom + // for (int row = 0; row < dimension; row++) { + // int y = readingUp ? dimension - 1 - row : row; + // for (int col = 0; col < 2; col++) { + // int xx = x - col; + // // Ignore bits covered by the function pattern + // if (!functionPattern.get(xx, y)) { + // // Read a bit + // AppendBit(currentByte, + // GetDataMaskBit(formatInfo.dataMask, xx, y, true) != getBit(bitMatrix, xx, y, formatInfo.isMirrored)); + // ++bitsRead; + // // If we've made a whole byte, save it off; save early if 2x2 data block. + // if (bitsRead == 8 || (bitsRead == 4 && hasD4mBlock && Size(result) == d4mBlockIndex - 1)) { + // result.push_back(std::exchange(currentByte, 0)); + // bitsRead = 0; + // } + // } + // } + // } + // readingUp = !readingUp; // switch directions + // } + // if (Size(result) != version.totalCodewords()) + // return {}; + + // return result; +} + +pub fn ReadCodewords( + bitMatrix: &BitMatrix, + version: VersionRef, + formatInfo: &FormatInformation, +) -> Result> { + if (!hasValidDimension(bitMatrix, version.isMicroQRCode())) { + return Err(Exceptions::FORMAT); + } + + if version.isMicroQRCode() { + ReadMQRCodewords(bitMatrix, version, formatInfo) + } else { + ReadQRCodewords(bitMatrix, version, formatInfo) + } +} diff --git a/src/qrcode/cpp_port/decoder.rs b/src/qrcode/cpp_port/decoder.rs index 3772591..211bc17 100644 --- a/src/qrcode/cpp_port/decoder.rs +++ b/src/qrcode/cpp_port/decoder.rs @@ -4,357 +4,450 @@ // */ // // SPDX-License-Identifier: Apache-2.0 -// #include "QRDecoder.h" +use crate::common::cpp_essentials::{DecoderResult, StructuredAppendInfo}; +use crate::common::reedsolomon::{ + get_predefined_genericgf, PredefinedGenericGF, ReedSolomonDecoder, +}; +use crate::common::{ + AIFlag, BitMatrix, BitSource, CharacterSet, DecoderRXingResult, ECIStringBuilder, Eci, Result, + SymbologyIdentifier, +}; +use crate::qrcode::cpp_port::bitmatrix_parser::{ + ReadCodewords, ReadFormatInformation, ReadVersion, +}; +use crate::qrcode::decoder::{DataBlock, ErrorCorrectionLevel, Mode, Version}; +use crate::Exceptions; -// #include "BitMatrix.h" -// #include "BitSource.h" -// #include "CharacterSet.h" -// #include "DecoderResult.h" -// #include "GenericGF.h" -// #include "QRBitMatrixParser.h" -// #include "QRCodecMode.h" -// #include "QRDataBlock.h" -// #include "QRFormatInformation.h" -// #include "QRVersion.h" -// #include "ReedSolomonDecoder.h" -// #include "StructuredAppend.h" -// #include "ZXAlgorithms.h" -// #include "ZXTestSupport.h" +/** +*

Given data and error-correction codewords received, possibly corrupted by errors, attempts to +* correct the errors in-place using Reed-Solomon error correction.

+* +* @param codewordBytes data and error correction codewords +* @param numDataCodewords number of codewords that are data bytes +* @return false if error correction fails +*/ +pub fn CorrectErrors(codewordBytes: &mut [u8], numDataCodewords: u32) -> Result { + // First read into an array of ints + // std::vector codewordsInts(codewordBytes.begin(), codewordBytes.end()); + let mut codewordsInts = codewordBytes.iter().copied().map(|b| b as i32).collect(); -// #include -// #include -// #include -// #include + let numECCodewords = ((codewordBytes.len() as u32) - numDataCodewords) as i32; + let rs = ReedSolomonDecoder::new(get_predefined_genericgf( + PredefinedGenericGF::QrCodeField256, + )); + if rs.decode(&mut codewordsInts, numECCodewords)? == 0 + // if (!ReedSolomonDecode(GenericGF::QRCodeField256(), codewordsInts, numECCodewords)) + { + return Ok(false); + } -// namespace ZXing::QRCode { + // Copy back into array of bytes -- only need to worry about the bytes that were data + // We don't care about errors in the error-correction codewords + codewordBytes[..numDataCodewords as usize].copy_from_slice( + &codewordsInts[..numDataCodewords as usize] + .into_iter() + .copied() + .map(|i| i as u8) + .collect::>(), + ); + // std::copy_n(codewordsInts.begin(), numDataCodewords, codewordBytes.begin()); -// /** -// *

Given data and error-correction codewords received, possibly corrupted by errors, attempts to -// * correct the errors in-place using Reed-Solomon error correction.

-// * -// * @param codewordBytes data and error correction codewords -// * @param numDataCodewords number of codewords that are data bytes -// * @return false if error correction fails -// */ -// static bool CorrectErrors(ByteArray& codewordBytes, int numDataCodewords) -// { -// // First read into an array of ints -// std::vector codewordsInts(codewordBytes.begin(), codewordBytes.end()); + Ok(true) +} -// int numECCodewords = Size(codewordBytes) - numDataCodewords; -// if (!ReedSolomonDecode(GenericGF::QRCodeField256(), codewordsInts, numECCodewords)) -// return false; +/** +* See specification GBT 18284-2000 +*/ +pub fn DecodeHanziSegment( + bits: &mut BitSource, + count: u32, + result: &mut ECIStringBuilder, +) -> Result<()> { + let mut count = count; -// // Copy back into array of bytes -- only need to worry about the bytes that were data -// // We don't care about errors in the error-correction codewords -// std::copy_n(codewordsInts.begin(), numDataCodewords, codewordBytes.begin()); -// return true; -// } + // Each character will require 2 bytes, decode as GB2312 + // There is no ECI value for GB2312, use GB18030 which is a superset + result.switch_encoding(CharacterSet::GB18030); + result.reserve(2 * count as usize); + while (count > 0) { + // Each 13 bits encodes a 2-byte character + let twoBytes = bits.readBits(13)?; + let mut assembledTwoBytes = ((twoBytes / 0x060) << 8) | (twoBytes % 0x060); + if (assembledTwoBytes < 0x00A00) { + // In the 0xA1A1 to 0xAAFE range + assembledTwoBytes += 0x0A1A1; + } else { + // In the 0xB0A1 to 0xFAFE range + assembledTwoBytes += 0x0A6A1; + } + *result += ((assembledTwoBytes >> 8) & 0xFF) as u8; + *result += (assembledTwoBytes & 0xFF) as u8; + count -= 1; + } + Ok(()) +} -// /** -// * See specification GBT 18284-2000 -// */ -// static void DecodeHanziSegment(BitSource& bits, int count, Content& result) -// { -// // Each character will require 2 bytes, decode as GB2312 -// // There is no ECI value for GB2312, use GB18030 which is a superset -// result.switchEncoding(CharacterSet::GB18030); -// result.reserve(2 * count); +pub fn DecodeKanjiSegment( + bits: &mut BitSource, + count: u32, + result: &mut ECIStringBuilder, +) -> Result<()> { + let mut count = count; + // Each character will require 2 bytes. Read the characters as 2-byte pairs + // and decode as Shift_JIS afterwards + result.switch_encoding(CharacterSet::Shift_JIS); + result.reserve(2 * count as usize); -// while (count > 0) { -// // Each 13 bits encodes a 2-byte character -// int twoBytes = bits.readBits(13); -// int assembledTwoBytes = ((twoBytes / 0x060) << 8) | (twoBytes % 0x060); -// if (assembledTwoBytes < 0x00A00) { -// // In the 0xA1A1 to 0xAAFE range -// assembledTwoBytes += 0x0A1A1; -// } else { -// // In the 0xB0A1 to 0xFAFE range -// assembledTwoBytes += 0x0A6A1; -// } -// result += narrow_cast((assembledTwoBytes >> 8) & 0xFF); -// result += narrow_cast(assembledTwoBytes & 0xFF); -// count--; -// } -// } + while (count > 0) { + // Each 13 bits encodes a 2-byte character + let twoBytes = bits.readBits(13)?; + let mut assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0); + if (assembledTwoBytes < 0x01F00) { + // In the 0x8140 to 0x9FFC range + assembledTwoBytes += 0x08140; + } else { + // In the 0xE040 to 0xEBBF range + assembledTwoBytes += 0x0C140; + } + *result += (assembledTwoBytes >> 8) as u8; + *result += (assembledTwoBytes) as u8; + count -= 1; + } + Ok(()) +} -// static void DecodeKanjiSegment(BitSource& bits, int count, Content& result) -// { -// // Each character will require 2 bytes. Read the characters as 2-byte pairs -// // and decode as Shift_JIS afterwards -// result.switchEncoding(CharacterSet::Shift_JIS); -// result.reserve(2 * count); +pub fn DecodeByteSegment( + bits: &mut BitSource, + count: u32, + result: &mut ECIStringBuilder, +) -> Result<()> { + result.switch_encoding(CharacterSet::Unknown); + result.reserve(count as usize); -// while (count > 0) { -// // Each 13 bits encodes a 2-byte character -// int twoBytes = bits.readBits(13); -// int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0); -// if (assembledTwoBytes < 0x01F00) { -// // In the 0x8140 to 0x9FFC range -// assembledTwoBytes += 0x08140; -// } else { -// // In the 0xE040 to 0xEBBF range -// assembledTwoBytes += 0x0C140; -// } -// result += narrow_cast(assembledTwoBytes >> 8); -// result += narrow_cast(assembledTwoBytes); -// count--; -// } -// } + for i in 0..count { + // for (int i = 0; i < count; i++) + *result += (bits.readBits(8)?) as u8; + } + Ok(()) +} -// static void DecodeByteSegment(BitSource& bits, int count, Content& result) -// { -// result.switchEncoding(CharacterSet::Unknown); -// result.reserve(count); +pub fn ToAlphaNumericChar(value: u32) -> Result { + let value = value as usize; + /** + * See ISO 18004:2006, 6.4.4 Table 5 + */ + const ALPHANUMERIC_CHARS: [char; 45] = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + ' ', '$', '%', '*', '+', '-', '.', '/', ':', + ]; -// for (int i = 0; i < count; i++) -// result += narrow_cast(bits.readBits(8)); -// } + if (value < 0 || value >= (ALPHANUMERIC_CHARS.len())) { + return Err(Exceptions::index_out_of_bounds_with( + "oAlphaNumericChar: out of range", + )); + } -// static char ToAlphaNumericChar(int value) -// { -// /** -// * See ISO 18004:2006, 6.4.4 Table 5 -// */ -// static const char ALPHANUMERIC_CHARS[] = { -// '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', -// 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', -// 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', -// ' ', '$', '%', '*', '+', '-', '.', '/', ':' -// }; + Ok(ALPHANUMERIC_CHARS[value]) +} -// if (value < 0 || value >= Size(ALPHANUMERIC_CHARS)) -// throw std::out_of_range("ToAlphaNumericChar: out of range"); +pub fn DecodeAlphanumericSegment( + bits: &mut BitSource, + count: u32, + result: &mut ECIStringBuilder, +) -> Result<()> { + let mut count = count; -// return ALPHANUMERIC_CHARS[value]; -// } + // Read two characters at a time + let mut buffer = String::new(); -// static void DecodeAlphanumericSegment(BitSource& bits, int count, Content& result) -// { -// // Read two characters at a time -// std::string buffer; -// while (count > 1) { -// int nextTwoCharsBits = bits.readBits(11); -// buffer += ToAlphaNumericChar(nextTwoCharsBits / 45); -// buffer += ToAlphaNumericChar(nextTwoCharsBits % 45); -// count -= 2; -// } -// if (count == 1) { -// // special case: one character left -// buffer += ToAlphaNumericChar(bits.readBits(6)); -// } -// // See section 6.4.8.1, 6.4.8.2 -// if (result.symbology.aiFlag != AIFlag::None) { -// // We need to massage the result a bit if in an FNC1 mode: -// for (size_t i = 0; i < buffer.length(); i++) { -// if (buffer[i] == '%') { -// if (i < buffer.length() - 1 && buffer[i + 1] == '%') { -// // %% is rendered as % -// buffer.erase(i + 1); -// } else { -// // In alpha mode, % should be converted to FNC1 separator 0x1D -// buffer[i] = static_cast(0x1D); -// } -// } -// } -// } + while count > 1 { + let nextTwoCharsBits = bits.readBits(11)?; + buffer.push(ToAlphaNumericChar(nextTwoCharsBits / 45)?); + buffer.push(ToAlphaNumericChar(nextTwoCharsBits % 45)?); + count -= 2; + } + if (count == 1) { + // special case: one character left + buffer.push(ToAlphaNumericChar(bits.readBits(6)?)?); + } + // See section 6.4.8.1, 6.4.8.2 + if (result.symbology.aiFlag != AIFlag::None) { + // We need to massage the result a bit if in an FNC1 mode: + for i in 0..buffer.len() { + // for (size_t i = 0; i < buffer.length(); i++) { + if (buffer + .chars() + .nth(i) + .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)? + == '%') + { + if (i < buffer.len() - 1 + && buffer + .chars() + .nth(i + 1) + .ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)? + == '%') + { + // %% is rendered as % + buffer.remove(i + 1); + // buffer.erase(i + 1); + } else { + // In alpha mode, % should be converted to FNC1 separator 0x1D + buffer.replace_range(i..i, &char::from(0x1D).to_string()); + // buffer[i] = static_cast(0x1D); + } + } + } + } -// result.switchEncoding(CharacterSet::ISO8859_1); -// result += buffer; -// } + result.switch_encoding(CharacterSet::ISO8859_1); + *result += buffer; -// static void DecodeNumericSegment(BitSource& bits, int count, Content& result) -// { -// result.switchEncoding(CharacterSet::ISO8859_1); -// result.reserve(count); + Ok(()) +} -// while (count) { -// int n = std::min(count, 3); -// int nDigits = bits.readBits(1 + 3 * n); // read 4, 7 or 10 bits into 1, 2 or 3 digits -// result.append(ZXing::ToString(nDigits, n)); -// count -= n; -// } -// } +pub fn DecodeNumericSegment( + bits: &mut BitSource, + count: u32, + result: &mut ECIStringBuilder, +) -> Result<()> { + let mut count = count; -// static ECI ParseECIValue(BitSource& bits) -// { -// int firstByte = bits.readBits(8); -// if ((firstByte & 0x80) == 0) { -// // just one byte -// return ECI(firstByte & 0x7F); -// } -// if ((firstByte & 0xC0) == 0x80) { -// // two bytes -// int secondByte = bits.readBits(8); -// return ECI(((firstByte & 0x3F) << 8) | secondByte); -// } -// if ((firstByte & 0xE0) == 0xC0) { -// // three bytes -// int secondThirdBytes = bits.readBits(16); -// return ECI(((firstByte & 0x1F) << 16) | secondThirdBytes); -// } -// throw FormatError("ParseECIValue: invalid value"); -// } + result.switch_encoding(CharacterSet::ISO8859_1); + result.reserve(count as usize); -// /** -// * QR codes encode mode indicators and terminator codes into a constant bit length of 4. -// * Micro QR codes have terminator codes that vary in bit length but are always longer than -// * the mode indicators. -// * M1 - 0 length mode code, 3 bits terminator code -// * M2 - 1 bit mode code, 5 bits terminator code -// * M3 - 2 bit mode code, 7 bits terminator code -// * M4 - 3 bit mode code, 9 bits terminator code -// * IsTerminator peaks into the bit stream to see if the current position is at the start of -// * a terminator code. If true, then the decoding can finish. If false, then the decoding -// * can read off the next mode code. -// * -// * See ISO 18004:2015, 7.4.1 Table 2 -// * -// * @param bits the stream of bits that might have a terminator code -// * @param version the QR or micro QR code version -// */ -// bool IsEndOfStream(const BitSource& bits, const Version& version) -// { -// const int bitsRequired = TerminatorBitsLength(version); -// const int bitsAvailable = std::min(bits.available(), bitsRequired); -// return bitsAvailable == 0 || bits.peakBits(bitsAvailable) == 0; -// } + while (count > 0) { + let n = std::cmp::min(count, 3); + let nDigits = bits.readBits(1 + 3 * n as usize)?; // read 4, 7 or 10 bits into 1, 2 or 3 digits + result.append_string(&crate::common::cpp_essentials::util::ToString( + nDigits as usize, + n as usize, + )?); + count -= n; + } -// /** -// *

QR Codes can encode text as bits in one of several modes, and can use multiple modes -// * in one QR Code. This method decodes the bits back into text.

-// * -// *

See ISO 18004:2006, 6.4.3 - 6.4.7

-// */ + Ok(()) +} + +pub fn ParseECIValue(bits: &mut BitSource) -> Result { + let firstByte = bits.readBits(8)?; + if ((firstByte & 0x80) == 0) { + // just one byte + return Ok(Eci::from(firstByte & 0x7F)); + } + if ((firstByte & 0xC0) == 0x80) { + // two bytes + let secondByte = bits.readBits(8)?; + return Ok(Eci::from(((firstByte & 0x3F) << 8) | secondByte)); + } + if ((firstByte & 0xE0) == 0xC0) { + // three bytes + let secondThirdBytes = bits.readBits(16)?; + return Ok(Eci::from(((firstByte & 0x1F) << 16) | secondThirdBytes)); + } + Err(Exceptions::format_with("ParseECIValue: invalid value")) +} + +/** + * QR codes encode mode indicators and terminator codes into a constant bit length of 4. + * Micro QR codes have terminator codes that vary in bit length but are always longer than + * the mode indicators. + * M1 - 0 length mode code, 3 bits terminator code + * M2 - 1 bit mode code, 5 bits terminator code + * M3 - 2 bit mode code, 7 bits terminator code + * M4 - 3 bit mode code, 9 bits terminator code + * IsTerminator peaks into the bit stream to see if the current position is at the start of + * a terminator code. If true, then the decoding can finish. If false, then the decoding + * can read off the next mode code. + * + * See ISO 18004:2015, 7.4.1 Table 2 + * + * @param bits the stream of bits that might have a terminator code + * @param version the QR or micro QR code version + */ +pub fn IsEndOfStream(bits: &mut BitSource, version: &Version) -> Result { + let bitsRequired = Mode::get_terminator_bit_length(version); //super::qr_codec_mode::TerminatorBitsLength(version); + let bitsAvailable = std::cmp::min(bits.available(), bitsRequired as usize); + Ok(bitsAvailable == 0 || bits.peak_bits(bitsAvailable)? == 0) +} + +/** +*

QR Codes can encode text as bits in one of several modes, and can use multiple modes +* in one QR Code. This method decodes the bits back into text.

+* +*

See ISO 18004:2006, 6.4.3 - 6.4.7

+*/ // ZXING_EXPORT_TEST_ONLY -// DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel ecLevel) -// { -// BitSource bits(bytes); -// Content result; -// Error error; -// result.symbology = {'Q', '1', 1}; -// StructuredAppendInfo structuredAppend; -// const int modeBitLength = CodecModeBitsLength(version); +pub fn DecodeBitStream( + bytes: &[u8], + version: &Version, + ecLevel: ErrorCorrectionLevel, +) -> Result> { + let mut bits = BitSource::new(bytes.to_vec()); + let mut result = ECIStringBuilder::default(); + // Error error; + result.symbology = SymbologyIdentifier { + code: b'Q', + modifier: b'1', + eciModifierOffset: 1, + aiFlag: AIFlag::None, + }; //{'Q', '1', 1}; + let mut structuredAppend = StructuredAppendInfo::default(); + let modeBitLength = Mode::get_codec_mode_bits_length(version); -// try -// { -// while(!IsEndOfStream(bits, version)) { -// CodecMode mode; -// if (modeBitLength == 0) -// mode = CodecMode::NUMERIC; // MicroQRCode version 1 is always NUMERIC and modeBitLength is 0 -// else -// mode = CodecModeForBits(bits.readBits(modeBitLength), version.isMicroQRCode()); + let res = (|| { + while (!IsEndOfStream(&mut bits, version)?) { + let mode: Mode; + if (modeBitLength == 0) { + mode = Mode::NUMERIC; // MicroQRCode version 1 is always NUMERIC and modeBitLength is 0 + } else { + mode = Mode::CodecModeForBits( + bits.readBits(modeBitLength as usize)?, + Some(version.isMicroQRCode()), + ); + } -// switch (mode) { -// case CodecMode::FNC1_FIRST_POSITION: -// // if (!result.empty()) // uncomment to enforce specification -// // throw FormatError("GS1 Indicator (FNC1 in first position) at illegal position"); -// result.symbology.modifier = '3'; -// result.symbology.aiFlag = AIFlag::GS1; // In Alphanumeric mode undouble doubled '%' and treat single '%' as -// break; -// case CodecMode::FNC1_SECOND_POSITION: -// if (!result.empty()) -// throw FormatError("AIM Application Indicator (FNC1 in second position) at illegal position"); -// result.symbology.modifier = '5'; // As above -// // ISO/IEC 18004:2015 7.4.8.3 AIM Application Indicator (FNC1 in second position), "00-99" or "A-Za-z" -// if (int appInd = bits.readBits(8); appInd < 100) // "00-09" -// result += ZXing::ToString(appInd, 2); -// else if ((appInd >= 165 && appInd <= 190) || (appInd >= 197 && appInd <= 222)) // "A-Za-z" -// result += narrow_cast(appInd - 100); -// else -// throw FormatError("Invalid AIM Application Indicator"); -// result.symbology.aiFlag = AIFlag::AIM; // see also above -// break; -// case CodecMode::STRUCTURED_APPEND: -// // sequence number and parity is added later to the result metadata -// // Read next 4 bits of index, 4 bits of symbol count, and 8 bits of parity data, then continue -// structuredAppend.index = bits.readBits(4); -// structuredAppend.count = bits.readBits(4) + 1; -// structuredAppend.id = std::to_string(bits.readBits(8)); -// break; -// case CodecMode::ECI: -// // Count doesn't apply to ECI -// result.switchEncoding(ParseECIValue(bits)); -// break; -// case CodecMode::HANZI: { -// // First handle Hanzi mode which does not start with character count -// // chinese mode contains a sub set indicator right after mode indicator -// if (int subset = bits.readBits(4); subset != 1) // GB2312_SUBSET is the only supported one right now -// throw FormatError("Unsupported HANZI subset"); -// int count = bits.readBits(CharacterCountBits(mode, version)); -// DecodeHanziSegment(bits, count, result); -// break; -// } -// default: { -// // "Normal" QR code modes: -// // How many characters will follow, encoded in this mode? -// int count = bits.readBits(CharacterCountBits(mode, version)); -// switch (mode) { -// case CodecMode::NUMERIC: DecodeNumericSegment(bits, count, result); break; -// case CodecMode::ALPHANUMERIC: DecodeAlphanumericSegment(bits, count, result); break; -// case CodecMode::BYTE: DecodeByteSegment(bits, count, result); break; -// case CodecMode::KANJI: DecodeKanjiSegment(bits, count, result); break; -// default: throw FormatError("Invalid CodecMode"); -// } -// break; -// } -// } -// } -// } catch (std::out_of_range& e) { // see BitSource::readBits -// error = FormatError("Truncated bit stream"); -// } catch (Error e) { -// error = std::move(e); -// } + match (mode) { + Mode::FNC1_FIRST_POSITION => { + // if (!result.empty()) // uncomment to enforce specification + // throw FormatError("GS1 Indicator (FNC1 in first position) at illegal position"); + result.symbology.modifier = b'3'; + result.symbology.aiFlag = AIFlag::GS1; // In Alphanumeric mode undouble doubled '%' and treat single '%' as + } + Mode::FNC1_SECOND_POSITION => { + if (!result.is_empty()) { + return Err(Exceptions::format_with("AIM Application Indicator (FNC1 in second position) at illegal position")); + // throw FormatError("AIM Application Indicator (FNC1 in second position) at illegal position"); + } + result.symbology.modifier = b'5'; // As above + // ISO/IEC 18004:2015 7.4.8.3 AIM Application Indicator (FNC1 in second position), "00-99" or "A-Za-z" + let appInd = bits.readBits(8)?; + if (appInd < 100) + // "00-09" + { + result += + crate::common::cpp_essentials::util::ToString(appInd as usize, 2)?; + } else if ((appInd >= 165 && appInd <= 190) || (appInd >= 197 && appInd <= 222)) + // "A-Za-z" + { + result += (appInd - 100) as u8; + } else { + return Err(Exceptions::format_with("Invalid AIM Application Indicator")); + // throw FormatError("Invalid AIM Application Indicator"); + } + result.symbology.aiFlag = AIFlag::AIM; // see also above + } + Mode::STRUCTURED_APPEND => { + // sequence number and parity is added later to the result metadata + // Read next 4 bits of index, 4 bits of symbol count, and 8 bits of parity data, then continue + structuredAppend.index = bits.readBits(4)? as i32; + structuredAppend.count = bits.readBits(4)? as i32 + 1; + structuredAppend.id = (bits.readBits(8)?).to_string(); //std::to_string(bits.readBits(8)); + } + Mode::ECI => { + // Count doesn't apply to ECI + result.switch_encoding(ParseECIValue(&mut bits)?.into()); + } + Mode::HANZI => { + // First handle Hanzi mode which does not start with character count + // chinese mode contains a sub set indicator right after mode indicator + let subset = bits.readBits(4)?; + if (subset != 1) + // GB2312_SUBSET is the only supported one right now + { + return Err(Exceptions::format_with("Unsupported HANZI subset")); + // throw FormatError("Unsupported HANZI subset"); + } + let count = bits.readBits(mode.CharacterCountBits(version) as usize)?; + DecodeHanziSegment(&mut bits, count, &mut result); + } + _ => { + // "Normal" QR code modes: + // How many characters will follow, encoded in this mode? + let count = bits.readBits(mode.CharacterCountBits(version) as usize)?; + match (mode) { + Mode::NUMERIC => DecodeNumericSegment(&mut bits, count, &mut result), + Mode::ALPHANUMERIC => { + DecodeAlphanumericSegment(&mut bits, count, &mut result) + } + Mode::BYTE => DecodeByteSegment(&mut bits, count, &mut result), + Mode::KANJI => DecodeKanjiSegment(&mut bits, count, &mut result), + _ => return Err(Exceptions::format_with("Invalid CodecMode")), //throw FormatError("Invalid CodecMode"); + }; + } + } + } + Ok(()) + })(); + // } catch (std::out_of_range& e) { // see BitSource::readBits + // error = FormatError("Truncated bit stream"); + // } catch (Error e) { + // error = std::move(e); + // } -// return DecoderResult(std::move(result)) -// .setError(std::move(error)) -// .setEcLevel(ToString(ecLevel)) -// .setVersionNumber(version.versionNumber()) -// .setStructuredAppend(structuredAppend); -// } + Ok(DecoderResult::with_eci_string_builder(result) + .withEcLevel(ecLevel.to_string()) + .withVersionNumber(version.getVersionNumber()) + .withStructuredAppend(structuredAppend)) -// DecoderResult Decode(const BitMatrix& bits) -// { -// const Version* pversion = ReadVersion(bits); -// if (!pversion) -// return FormatError("Invalid version"); -// const Version& version = *pversion; + // return DecoderResult(std::move(result)) + // .setError(std::move(error)) + // .setEcLevel(ToString(ecLevel)) + // .setVersionNumber(version.versionNumber()) + // .setStructuredAppend(structuredAppend); +} -// auto formatInfo = ReadFormatInformation(bits, version.isMicroQRCode()); -// if (!formatInfo.isValid()) -// return FormatError("Invalid format information"); +pub fn Decode(bits: &BitMatrix) -> Result> { + let Ok(pversion) = ReadVersion(bits) else { + return Err(Exceptions::format_with("Invalid version")) + }; + let version = pversion; -// // Read codewords -// ByteArray codewords = ReadCodewords(bits, version, formatInfo); -// if (codewords.empty()) -// return FormatError("Failed to read codewords"); + let Ok(formatInfo) = ReadFormatInformation(bits, version.isMicroQRCode()) else { + return Err(Exceptions::format_with("Invalid format information")) + }; -// // Separate into data blocks -// std::vector dataBlocks = DataBlock::GetDataBlocks(codewords, version, formatInfo.ecLevel); -// if (dataBlocks.empty()) -// return FormatError("Failed to get data blocks"); + // Read codewords + let codewords = ReadCodewords(bits, &version, &formatInfo)?; + if (codewords.is_empty()) { + return Err(Exceptions::format_with("Failed to read codewords")); + } -// // Count total number of data bytes -// const auto op = [](auto totalBytes, const auto& dataBlock){ return totalBytes + dataBlock.numDataCodewords();}; -// const auto totalBytes = std::accumulate(std::begin(dataBlocks), std::end(dataBlocks), int{}, op); -// ByteArray resultBytes(totalBytes); -// auto resultIterator = resultBytes.begin(); + // Separate into data blocks + let dataBlocks: Vec = + DataBlock::getDataBlocks(&codewords, &version, formatInfo.error_correction_level)?; + if (dataBlocks.is_empty()) { + return Err(Exceptions::format_with("Failed to get data blocks")); + } -// // Error-correct and copy data blocks together into a stream of bytes -// for (auto& dataBlock : dataBlocks) -// { -// ByteArray& codewordBytes = dataBlock.codewords(); -// int numDataCodewords = dataBlock.numDataCodewords(); + // Count total number of data bytes + let op = |totalBytes, dataBlock: &DataBlock| totalBytes + dataBlock.getNumDataCodewords(); + let totalBytes = dataBlocks.iter().fold(0, op); // std::accumulate(std::begin(dataBlocks), std::end(dataBlocks), int{}, op); + let mut resultBytes = vec![0u8; totalBytes as usize]; + let mut resultIterator = 0; //resultBytes.begin(); -// if (!CorrectErrors(codewordBytes, numDataCodewords)) -// return ChecksumError(); + // Error-correct and copy data blocks together into a stream of bytes + for dataBlock in dataBlocks.iter() { + let mut codewordBytes = dataBlock.getCodewords().to_vec(); + let numDataCodewords = dataBlock.getNumDataCodewords() as usize; -// resultIterator = std::copy_n(codewordBytes.begin(), numDataCodewords, resultIterator); -// } + if (!CorrectErrors(&mut codewordBytes, numDataCodewords as u32)?) { + return Err(Exceptions::CHECKSUM); + } -// // Decode the contents of that stream of bytes -// return DecodeBitStream(std::move(resultBytes), version, formatInfo.ecLevel).setIsMirrored(formatInfo.isMirrored); -// } + // resultIterator = std::copy_n(codewordBytes.begin(), numDataCodewords, resultIterator); + resultBytes[resultIterator..numDataCodewords] + .copy_from_slice(&codewordBytes[..numDataCodewords]); + resultIterator += numDataCodewords; + } + + // Decode the contents of that stream of bytes + Ok( + DecodeBitStream(&resultBytes, &version, formatInfo.error_correction_level)? + .withIsMirrored(formatInfo.isMirrored), + ) +} // } // namespace ZXing::QRCode diff --git a/src/qrcode/cpp_port/mod.rs b/src/qrcode/cpp_port/mod.rs index 7b4f317..464babf 100644 --- a/src/qrcode/cpp_port/mod.rs +++ b/src/qrcode/cpp_port/mod.rs @@ -1,2 +1,4 @@ pub mod decoder; pub mod detector; + +mod bitmatrix_parser; diff --git a/src/qrcode/decoder/error_correction_level.rs b/src/qrcode/decoder/error_correction_level.rs index 0d5c734..b3be3bc 100644 --- a/src/qrcode/decoder/error_correction_level.rs +++ b/src/qrcode/decoder/error_correction_level.rs @@ -14,6 +14,7 @@ * limitations under the License. */ +use std::fmt::{self, Display}; use std::str::FromStr; use crate::common::Result; @@ -118,3 +119,15 @@ impl FromStr for ErrorCorrectionLevel { ))); } } + +impl Display for ErrorCorrectionLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + ErrorCorrectionLevel::L => "L", + ErrorCorrectionLevel::M => "M", + ErrorCorrectionLevel::Q => "Q", + ErrorCorrectionLevel::H => "H", + ErrorCorrectionLevel::Invalid => "_", + }) + } +} diff --git a/src/qrcode/decoder/format_information.rs b/src/qrcode/decoder/format_information.rs index c0256a6..65800c2 100644 --- a/src/qrcode/decoder/format_information.rs +++ b/src/qrcode/decoder/format_information.rs @@ -72,6 +72,7 @@ pub struct FormatInformation { pub error_correction_level: ErrorCorrectionLevel, pub data_mask: u8, pub microVersion: u32, + pub isMirrored: bool, } impl Default for FormatInformation { @@ -81,6 +82,7 @@ impl Default for FormatInformation { error_correction_level: ErrorCorrectionLevel::Invalid, data_mask: Default::default(), microVersion: 0, + isMirrored: false, } } } @@ -96,6 +98,7 @@ impl FormatInformation { microVersion: 0, error_correction_level: errorCorrectionLevel, data_mask: dataMask, + isMirrored: false, }) } diff --git a/src/qrcode/decoder/mode.rs b/src/qrcode/decoder/mode.rs index ab726f1..06440d7 100644 --- a/src/qrcode/decoder/mode.rs +++ b/src/qrcode/decoder/mode.rs @@ -121,6 +121,32 @@ impl Mode { Mode::HANZI => 0x0D, } } + + pub const fn get_terminator_bit_length(version: &Version) -> u8 { + todo!() + } + pub const fn get_codec_mode_bits_length(version: &Version) -> u8 { + todo!() + } + /** + * @param bits variable number of bits encoding a QR Code data mode + * @param isMicro is this a MicroQRCode + * @return Mode encoded by these bits + * @throws FormatError if bits do not correspond to a known mode + */ + pub fn CodecModeForBits(bits: u32, isMicro: Option) -> Self { + let isMicro = isMicro.unwrap_or(false); + todo!() + } + + /** + * @param version version in question + * @return number of bits used, in this QR Code symbol {@link Version}, to encode the + * count of characters that will follow encoded in this Mode + */ + pub fn CharacterCountBits(&self, version: &Version) -> u32 { + todo!() + } } impl From for u8 { From 607bb28101baa89c5010771c1a69504dc7a84d07 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 28 Mar 2023 11:16:05 -0500 Subject: [PATCH 24/72] micro versions --- src/common/bit_source.rs | 48 ++++- .../base_extentions/qrcode_version.rs | 2 +- src/qrcode/decoder/version.rs | 202 +++++++++++------- 3 files changed, 167 insertions(+), 85 deletions(-) diff --git a/src/common/bit_source.rs b/src/common/bit_source.rs index 16cba87..22233df 100644 --- a/src/common/bit_source.rs +++ b/src/common/bit_source.rs @@ -117,7 +117,53 @@ impl BitSource { } pub fn peak_bits(&self, numBits: usize) -> Result { - todo!() + if !(1..=32).contains(&numBits) || numBits > self.available() { + return Err(Exceptions::illegal_argument_with(numBits.to_string())); + } + + let mut bit_offset = self.bit_offset; + let mut byte_offset = self.byte_offset; + + let mut result: u32 = 0; + + let mut num_bits = numBits; + + // First, read remainder from current byte + if self.bit_offset > 0 { + let bitsLeft = 8 - self.bit_offset; + let toRead = cmp::min(num_bits, bitsLeft); + let bitsToNotRead = bitsLeft - toRead; + let mask = (0xFF >> (8 - toRead)) << bitsToNotRead; + + result = (self.bytes[self.byte_offset] & mask) as u32 >> bitsToNotRead; + num_bits -= toRead; + bit_offset += toRead; + if bit_offset == 8 { + bit_offset = 0; + byte_offset += 1; + } + } + + // Next read whole bytes + if num_bits > 0 { + while num_bits >= 8 { + result = (result << 8) | self.bytes[self.byte_offset] as u32; + // result = ((result as u16) << 8) as u8 | (self.bytes[self.byte_offset]); + byte_offset += 1; + num_bits -= 8; + } + + // Finally read a partial byte + if num_bits > 0 { + let bits_to_not_read = 8 - num_bits; + let mask = (0xFF >> bits_to_not_read) << bits_to_not_read; + result = (result << num_bits) + | ((self.bytes[self.byte_offset] & mask) as u32 >> bits_to_not_read); + bit_offset += num_bits; + } + } + + Ok(result) } /** diff --git a/src/common/cpp_essentials/base_extentions/qrcode_version.rs b/src/common/cpp_essentials/base_extentions/qrcode_version.rs index 49cad90..1ca2bda 100644 --- a/src/common/cpp_essentials/base_extentions/qrcode_version.rs +++ b/src/common/cpp_essentials/base_extentions/qrcode_version.rs @@ -67,6 +67,6 @@ impl Version { } pub fn isMicroQRCode(&self) -> bool { - todo!() + self.is_micro } } diff --git a/src/qrcode/decoder/version.rs b/src/qrcode/decoder/version.rs index a325780..59c54dc 100755 --- a/src/qrcode/decoder/version.rs +++ b/src/qrcode/decoder/version.rs @@ -28,6 +28,7 @@ use once_cell::sync::Lazy; pub type VersionRef = &'static Version; static VERSIONS: Lazy> = Lazy::new(Version::buildVersions); +static MICRO_VERSIONS: Lazy> = Lazy::new(Version::build_micro_versions); /** * See ISO 18004:2006 Annex D. @@ -53,9 +54,10 @@ pub struct Version { alignmentPatternCenters: Vec, ecBlocks: Vec, totalCodewords: u32, + pub(crate) is_micro: bool, } impl Version { - fn new(versionNumber: u32, alignmentPatternCenters: Vec, ecBlocks: Vec) -> Self { + fn new(versionNumber: u32, alignmentPatternCenters: Vec, ecBlocks: [ECBlocks;4]) -> Self { let mut total = 0; let ecCodewords = ecBlocks[0].getECCodewordsPerBlock(); let ecbArray = ecBlocks[0].getECBlocks(); @@ -68,8 +70,28 @@ impl Version { Self { versionNumber, alignmentPatternCenters, + ecBlocks: ecBlocks.to_vec(), + totalCodewords: total, + is_micro: false + } + } + + fn new_micro(versionNumber: u32, ecBlocks: Vec) -> Self { + let mut total = 0; + let ecCodewords = ecBlocks[0].getECCodewordsPerBlock(); + let ecbArray = ecBlocks[0].getECBlocks(); + let mut i = 0; + while i < ecbArray.len() { + total += ecbArray[i].getCount() * (ecbArray[i].getDataCodewords() + ecCodewords); + i += 1; + } + + Self { + versionNumber, + alignmentPatternCenters: Vec::default(), ecBlocks, totalCodewords: total, + is_micro: true } } @@ -181,6 +203,20 @@ impl Version { Ok(bitMatrix) } +pub fn build_micro_versions() -> Vec { + vec![ + Version::new_micro(1, vec![ECBlocks::new(2, vec![ECB::new(1,3)])]), + Version::new_micro(2, vec![ECBlocks::new(5, vec![ECB::new(1,5)]), ECBlocks::new(6, vec![ECB::new(1,4)])]), + Version::new_micro(3, vec![ECBlocks::new(6, vec![ECB::new(1,11)]), ECBlocks::new(8, vec![ECB::new(1,9)])]), + Version::new_micro(4, vec![ECBlocks::new(8, vec![ECB::new(1,16)]), ECBlocks::new(10, vec![ECB::new(1,14)]), ECBlocks::new(14, vec![ECB::new(1,10)])]), + ] + // static const Version allVersions[] = { + // {1, {2, 1, 3, 0, 0}}, + // {2, {5, 1, 5, 0, 0, 6, 1, 4, 0, 0}}, + // {3, {6, 1, 11, 0, 0, 8, 1, 9, 0, 0}}, + // {4, {8, 1, 16, 0, 0, 10, 1, 14, 0, 0, 14, 1, 10, 0, 0}}}; +} + /** * See ISO 18004:2006 6.5.1 Table 9 */ @@ -189,402 +225,402 @@ impl Version { Version::new( 1, Vec::from([]), - Vec::from([ + [ ECBlocks::new(7, Vec::from([ECB::new(1, 19)])), ECBlocks::new(10, Vec::from([ECB::new(1, 16)])), ECBlocks::new(13, Vec::from([ECB::new(1, 13)])), ECBlocks::new(17, Vec::from([ECB::new(1, 9)])), - ]), + ], ), Version::new( 2, Vec::from([6, 18]), - Vec::from([ + [ ECBlocks::new(10, Vec::from([ECB::new(1, 34)])), ECBlocks::new(16, Vec::from([ECB::new(1, 28)])), ECBlocks::new(22, Vec::from([ECB::new(1, 22)])), ECBlocks::new(28, Vec::from([ECB::new(1, 16)])), - ]), + ], ), Version::new( 3, Vec::from([6, 22]), - Vec::from([ + [ ECBlocks::new(15, Vec::from([ECB::new(1, 55)])), ECBlocks::new(26, Vec::from([ECB::new(1, 44)])), ECBlocks::new(18, Vec::from([ECB::new(2, 17)])), ECBlocks::new(22, Vec::from([ECB::new(2, 13)])), - ]), + ], ), Version::new( 4, Vec::from([6, 26]), - Vec::from([ + [ ECBlocks::new(20, Vec::from([ECB::new(1, 80)])), ECBlocks::new(18, Vec::from([ECB::new(2, 32)])), ECBlocks::new(26, Vec::from([ECB::new(2, 24)])), ECBlocks::new(16, Vec::from([ECB::new(4, 9)])), - ]), + ], ), Version::new( 5, Vec::from([6, 30]), - Vec::from([ + [ ECBlocks::new(26, Vec::from([ECB::new(1, 108)])), ECBlocks::new(24, Vec::from([ECB::new(2, 43)])), ECBlocks::new(18, Vec::from([ECB::new(2, 15), ECB::new(2, 16)])), ECBlocks::new(22, Vec::from([ECB::new(2, 11), ECB::new(2, 12)])), - ]), + ], ), Version::new( 6, Vec::from([6, 34]), - Vec::from([ + [ ECBlocks::new(18, Vec::from([ECB::new(2, 68)])), ECBlocks::new(16, Vec::from([ECB::new(4, 27)])), ECBlocks::new(24, Vec::from([ECB::new(4, 19)])), ECBlocks::new(28, Vec::from([ECB::new(4, 15)])), - ]), + ], ), Version::new( 7, Vec::from([6, 22, 38]), - Vec::from([ + [ ECBlocks::new(20, Vec::from([ECB::new(2, 78)])), ECBlocks::new(18, Vec::from([ECB::new(4, 31)])), ECBlocks::new(18, Vec::from([ECB::new(2, 14), ECB::new(4, 15)])), ECBlocks::new(26, Vec::from([ECB::new(4, 13), ECB::new(1, 14)])), - ]), + ], ), Version::new( 8, Vec::from([6, 24, 42]), - Vec::from([ + [ ECBlocks::new(24, Vec::from([ECB::new(2, 97)])), ECBlocks::new(22, Vec::from([ECB::new(2, 38), ECB::new(2, 39)])), ECBlocks::new(22, Vec::from([ECB::new(4, 18), ECB::new(2, 19)])), ECBlocks::new(26, Vec::from([ECB::new(4, 14), ECB::new(2, 15)])), - ]), + ], ), Version::new( 9, Vec::from([6, 26, 46]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(2, 116)])), ECBlocks::new(22, Vec::from([ECB::new(3, 36), ECB::new(2, 37)])), ECBlocks::new(20, Vec::from([ECB::new(4, 16), ECB::new(4, 17)])), ECBlocks::new(24, Vec::from([ECB::new(4, 12), ECB::new(4, 13)])), - ]), + ], ), Version::new( 10, Vec::from([6, 28, 50]), - Vec::from([ + [ ECBlocks::new(18, Vec::from([ECB::new(2, 68), ECB::new(2, 69)])), ECBlocks::new(26, Vec::from([ECB::new(4, 43), ECB::new(1, 44)])), ECBlocks::new(24, Vec::from([ECB::new(6, 19), ECB::new(2, 20)])), ECBlocks::new(28, Vec::from([ECB::new(6, 15), ECB::new(2, 16)])), - ]), + ], ), Version::new( 11, Vec::from([6, 30, 54]), - Vec::from([ + [ ECBlocks::new(20, Vec::from([ECB::new(4, 81)])), ECBlocks::new(30, Vec::from([ECB::new(1, 50), ECB::new(4, 51)])), ECBlocks::new(28, Vec::from([ECB::new(4, 22), ECB::new(4, 23)])), ECBlocks::new(24, Vec::from([ECB::new(3, 12), ECB::new(8, 13)])), - ]), + ], ), Version::new( 12, Vec::from([6, 32, 58]), - Vec::from([ + [ ECBlocks::new(24, Vec::from([ECB::new(2, 92), ECB::new(2, 93)])), ECBlocks::new(22, Vec::from([ECB::new(6, 36), ECB::new(2, 37)])), ECBlocks::new(26, Vec::from([ECB::new(4, 20), ECB::new(6, 21)])), ECBlocks::new(28, Vec::from([ECB::new(7, 14), ECB::new(4, 15)])), - ]), + ], ), Version::new( 13, Vec::from([6, 34, 62]), - Vec::from([ + [ ECBlocks::new(26, Vec::from([ECB::new(4, 107)])), ECBlocks::new(22, Vec::from([ECB::new(8, 37), ECB::new(1, 38)])), ECBlocks::new(24, Vec::from([ECB::new(8, 20), ECB::new(4, 21)])), ECBlocks::new(22, Vec::from([ECB::new(12, 11), ECB::new(4, 12)])), - ]), + ], ), Version::new( 14, Vec::from([6, 26, 46, 66]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(3, 115), ECB::new(1, 116)])), ECBlocks::new(24, Vec::from([ECB::new(4, 40), ECB::new(5, 41)])), ECBlocks::new(20, Vec::from([ECB::new(11, 16), ECB::new(5, 17)])), ECBlocks::new(24, Vec::from([ECB::new(11, 12), ECB::new(5, 13)])), - ]), + ], ), Version::new( 15, Vec::from([6, 26, 48, 70]), - Vec::from([ + [ ECBlocks::new(22, Vec::from([ECB::new(5, 87), ECB::new(1, 88)])), ECBlocks::new(24, Vec::from([ECB::new(5, 41), ECB::new(5, 42)])), ECBlocks::new(30, Vec::from([ECB::new(5, 24), ECB::new(7, 25)])), ECBlocks::new(24, Vec::from([ECB::new(11, 12), ECB::new(7, 13)])), - ]), + ], ), Version::new( 16, Vec::from([6, 26, 50, 74]), - Vec::from([ + [ ECBlocks::new(24, Vec::from([ECB::new(5, 98), ECB::new(1, 99)])), ECBlocks::new(28, Vec::from([ECB::new(7, 45), ECB::new(3, 46)])), ECBlocks::new(24, Vec::from([ECB::new(15, 19), ECB::new(2, 20)])), ECBlocks::new(30, Vec::from([ECB::new(3, 15), ECB::new(13, 16)])), - ]), + ], ), Version::new( 17, Vec::from([6, 30, 54, 78]), - Vec::from([ + [ ECBlocks::new(28, Vec::from([ECB::new(1, 107), ECB::new(5, 108)])), ECBlocks::new(28, Vec::from([ECB::new(10, 46), ECB::new(1, 47)])), ECBlocks::new(28, Vec::from([ECB::new(1, 22), ECB::new(15, 23)])), ECBlocks::new(28, Vec::from([ECB::new(2, 14), ECB::new(17, 15)])), - ]), + ], ), Version::new( 18, Vec::from([6, 30, 56, 82]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(5, 120), ECB::new(1, 121)])), ECBlocks::new(26, Vec::from([ECB::new(9, 43), ECB::new(4, 44)])), ECBlocks::new(28, Vec::from([ECB::new(17, 22), ECB::new(1, 23)])), ECBlocks::new(28, Vec::from([ECB::new(2, 14), ECB::new(19, 15)])), - ]), + ], ), Version::new( 19, Vec::from([6, 30, 58, 86]), - Vec::from([ + [ ECBlocks::new(28, Vec::from([ECB::new(3, 113), ECB::new(4, 114)])), ECBlocks::new(26, Vec::from([ECB::new(3, 44), ECB::new(11, 45)])), ECBlocks::new(26, Vec::from([ECB::new(17, 21), ECB::new(4, 22)])), ECBlocks::new(26, Vec::from([ECB::new(9, 13), ECB::new(16, 14)])), - ]), + ], ), Version::new( 20, Vec::from([6, 34, 62, 90]), - Vec::from([ + [ ECBlocks::new(28, Vec::from([ECB::new(3, 107), ECB::new(5, 108)])), ECBlocks::new(26, Vec::from([ECB::new(3, 41), ECB::new(13, 42)])), ECBlocks::new(30, Vec::from([ECB::new(15, 24), ECB::new(5, 25)])), ECBlocks::new(28, Vec::from([ECB::new(15, 15), ECB::new(10, 16)])), - ]), + ], ), Version::new( 21, Vec::from([6, 28, 50, 72, 94]), - Vec::from([ + [ ECBlocks::new(28, Vec::from([ECB::new(4, 116), ECB::new(4, 117)])), ECBlocks::new(26, Vec::from([ECB::new(17, 42)])), ECBlocks::new(28, Vec::from([ECB::new(17, 22), ECB::new(6, 23)])), ECBlocks::new(30, Vec::from([ECB::new(19, 16), ECB::new(6, 17)])), - ]), + ], ), Version::new( 22, Vec::from([6, 26, 50, 74, 98]), - Vec::from([ + [ ECBlocks::new(28, Vec::from([ECB::new(2, 111), ECB::new(7, 112)])), ECBlocks::new(28, Vec::from([ECB::new(17, 46)])), ECBlocks::new(30, Vec::from([ECB::new(7, 24), ECB::new(16, 25)])), ECBlocks::new(24, Vec::from([ECB::new(34, 13)])), - ]), + ], ), Version::new( 23, Vec::from([6, 30, 54, 78, 102]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(4, 121), ECB::new(5, 122)])), ECBlocks::new(28, Vec::from([ECB::new(4, 47), ECB::new(14, 48)])), ECBlocks::new(30, Vec::from([ECB::new(11, 24), ECB::new(14, 25)])), ECBlocks::new(30, Vec::from([ECB::new(16, 15), ECB::new(14, 16)])), - ]), + ], ), Version::new( 24, Vec::from([6, 28, 54, 80, 106]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(6, 117), ECB::new(4, 118)])), ECBlocks::new(28, Vec::from([ECB::new(6, 45), ECB::new(14, 46)])), ECBlocks::new(30, Vec::from([ECB::new(11, 24), ECB::new(16, 25)])), ECBlocks::new(30, Vec::from([ECB::new(30, 16), ECB::new(2, 17)])), - ]), + ], ), Version::new( 25, Vec::from([6, 32, 58, 84, 110]), - Vec::from([ + [ ECBlocks::new(26, Vec::from([ECB::new(8, 106), ECB::new(4, 107)])), ECBlocks::new(28, Vec::from([ECB::new(8, 47), ECB::new(13, 48)])), ECBlocks::new(30, Vec::from([ECB::new(7, 24), ECB::new(22, 25)])), ECBlocks::new(30, Vec::from([ECB::new(22, 15), ECB::new(13, 16)])), - ]), + ], ), Version::new( 26, Vec::from([6, 30, 58, 86, 114]), - Vec::from([ + [ ECBlocks::new(28, Vec::from([ECB::new(10, 114), ECB::new(2, 115)])), ECBlocks::new(28, Vec::from([ECB::new(19, 46), ECB::new(4, 47)])), ECBlocks::new(28, Vec::from([ECB::new(28, 22), ECB::new(6, 23)])), ECBlocks::new(30, Vec::from([ECB::new(33, 16), ECB::new(4, 17)])), - ]), + ], ), Version::new( 27, Vec::from([6, 34, 62, 90, 118]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(8, 122), ECB::new(4, 123)])), ECBlocks::new(28, Vec::from([ECB::new(22, 45), ECB::new(3, 46)])), ECBlocks::new(30, Vec::from([ECB::new(8, 23), ECB::new(26, 24)])), ECBlocks::new(30, Vec::from([ECB::new(12, 15), ECB::new(28, 16)])), - ]), + ], ), Version::new( 28, Vec::from([6, 26, 50, 74, 98, 122]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(3, 117), ECB::new(10, 118)])), ECBlocks::new(28, Vec::from([ECB::new(3, 45), ECB::new(23, 46)])), ECBlocks::new(30, Vec::from([ECB::new(4, 24), ECB::new(31, 25)])), ECBlocks::new(30, Vec::from([ECB::new(11, 15), ECB::new(31, 16)])), - ]), + ], ), Version::new( 29, Vec::from([6, 30, 54, 78, 102, 126]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(7, 116), ECB::new(7, 117)])), ECBlocks::new(28, Vec::from([ECB::new(21, 45), ECB::new(7, 46)])), ECBlocks::new(30, Vec::from([ECB::new(1, 23), ECB::new(37, 24)])), ECBlocks::new(30, Vec::from([ECB::new(19, 15), ECB::new(26, 16)])), - ]), + ], ), Version::new( 30, Vec::from([6, 26, 52, 78, 104, 130]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(5, 115), ECB::new(10, 116)])), ECBlocks::new(28, Vec::from([ECB::new(19, 47), ECB::new(10, 48)])), ECBlocks::new(30, Vec::from([ECB::new(15, 24), ECB::new(25, 25)])), ECBlocks::new(30, Vec::from([ECB::new(23, 15), ECB::new(25, 16)])), - ]), + ], ), Version::new( 31, Vec::from([6, 30, 56, 82, 108, 134]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(13, 115), ECB::new(3, 116)])), ECBlocks::new(28, Vec::from([ECB::new(2, 46), ECB::new(29, 47)])), ECBlocks::new(30, Vec::from([ECB::new(42, 24), ECB::new(1, 25)])), ECBlocks::new(30, Vec::from([ECB::new(23, 15), ECB::new(28, 16)])), - ]), + ], ), Version::new( 32, Vec::from([6, 34, 60, 86, 112, 138]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(17, 115)])), ECBlocks::new(28, Vec::from([ECB::new(10, 46), ECB::new(23, 47)])), ECBlocks::new(30, Vec::from([ECB::new(10, 24), ECB::new(35, 25)])), ECBlocks::new(30, Vec::from([ECB::new(19, 15), ECB::new(35, 16)])), - ]), + ], ), Version::new( 33, Vec::from([6, 30, 58, 86, 114, 142]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(17, 115), ECB::new(1, 116)])), ECBlocks::new(28, Vec::from([ECB::new(14, 46), ECB::new(21, 47)])), ECBlocks::new(30, Vec::from([ECB::new(29, 24), ECB::new(19, 25)])), ECBlocks::new(30, Vec::from([ECB::new(11, 15), ECB::new(46, 16)])), - ]), + ], ), Version::new( 34, Vec::from([6, 34, 62, 90, 118, 146]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(13, 115), ECB::new(6, 116)])), ECBlocks::new(28, Vec::from([ECB::new(14, 46), ECB::new(23, 47)])), ECBlocks::new(30, Vec::from([ECB::new(44, 24), ECB::new(7, 25)])), ECBlocks::new(30, Vec::from([ECB::new(59, 16), ECB::new(1, 17)])), - ]), + ], ), Version::new( 35, Vec::from([6, 30, 54, 78, 102, 126, 150]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(12, 121), ECB::new(7, 122)])), ECBlocks::new(28, Vec::from([ECB::new(12, 47), ECB::new(26, 48)])), ECBlocks::new(30, Vec::from([ECB::new(39, 24), ECB::new(14, 25)])), ECBlocks::new(30, Vec::from([ECB::new(22, 15), ECB::new(41, 16)])), - ]), + ], ), Version::new( 36, Vec::from([6, 24, 50, 76, 102, 128, 154]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(6, 121), ECB::new(14, 122)])), ECBlocks::new(28, Vec::from([ECB::new(6, 47), ECB::new(34, 48)])), ECBlocks::new(30, Vec::from([ECB::new(46, 24), ECB::new(10, 25)])), ECBlocks::new(30, Vec::from([ECB::new(2, 15), ECB::new(64, 16)])), - ]), + ], ), Version::new( 37, Vec::from([6, 28, 54, 80, 106, 132, 158]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(17, 122), ECB::new(4, 123)])), ECBlocks::new(28, Vec::from([ECB::new(29, 46), ECB::new(14, 47)])), ECBlocks::new(30, Vec::from([ECB::new(49, 24), ECB::new(10, 25)])), ECBlocks::new(30, Vec::from([ECB::new(24, 15), ECB::new(46, 16)])), - ]), + ], ), Version::new( 38, Vec::from([6, 32, 58, 84, 110, 136, 162]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(4, 122), ECB::new(18, 123)])), ECBlocks::new(28, Vec::from([ECB::new(13, 46), ECB::new(32, 47)])), ECBlocks::new(30, Vec::from([ECB::new(48, 24), ECB::new(14, 25)])), ECBlocks::new(30, Vec::from([ECB::new(42, 15), ECB::new(32, 16)])), - ]), + ], ), Version::new( 39, Vec::from([6, 26, 54, 82, 110, 138, 166]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(20, 117), ECB::new(4, 118)])), ECBlocks::new(28, Vec::from([ECB::new(40, 47), ECB::new(7, 48)])), ECBlocks::new(30, Vec::from([ECB::new(43, 24), ECB::new(22, 25)])), ECBlocks::new(30, Vec::from([ECB::new(10, 15), ECB::new(67, 16)])), - ]), + ], ), Version::new( 40, Vec::from([6, 30, 58, 86, 114, 142, 170]), - Vec::from([ + [ ECBlocks::new(30, Vec::from([ECB::new(19, 118), ECB::new(6, 119)])), ECBlocks::new(28, Vec::from([ECB::new(18, 47), ECB::new(31, 48)])), ECBlocks::new(30, Vec::from([ECB::new(34, 24), ECB::new(34, 25)])), ECBlocks::new(30, Vec::from([ECB::new(20, 15), ECB::new(61, 16)])), - ]), + ], ), ]) /* new Version(4, new int[]{6, 26}, @@ -916,7 +952,7 @@ impl fmt::Display for Version { * each set of blocks. It also holds the number of error-correction codewords per block since it * will be the same across all blocks within one version.

*/ -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ECBlocks { ecCodewordsPerBlock: u32, ecBlocks: Vec, @@ -956,7 +992,7 @@ impl ECBlocks { * This includes the number of data codewords, and the number of times a block with these * parameters is used consecutively in the QR code version's format.

*/ -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct ECB { count: u32, dataCodewords: u32, From 74a459f65a99b29e4342402558b335e3be337b32 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 28 Mar 2023 16:31:41 -0500 Subject: [PATCH 25/72] continue removing todo!()s --- .../base_extentions/qrcode_version.rs | 2 +- src/common/cpp_essentials/decoder_result.rs | 6 ++- src/qrcode/cpp_port/decoder.rs | 2 +- src/qrcode/decoder/mode.rs | 38 +++++++++++-- src/qrcode/decoder/version.rs | 53 +++++++++++++------ 5 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/common/cpp_essentials/base_extentions/qrcode_version.rs b/src/common/cpp_essentials/base_extentions/qrcode_version.rs index 1ca2bda..e0415a3 100644 --- a/src/common/cpp_essentials/base_extentions/qrcode_version.rs +++ b/src/common/cpp_essentials/base_extentions/qrcode_version.rs @@ -66,7 +66,7 @@ impl Version { return Err(Exceptions::ILLEGAL_STATE); } - pub fn isMicroQRCode(&self) -> bool { + pub const fn isMicroQRCode(&self) -> bool { self.is_micro } } diff --git a/src/common/cpp_essentials/decoder_result.rs b/src/common/cpp_essentials/decoder_result.rs index 0906e13..d8a9ab5 100644 --- a/src/common/cpp_essentials/decoder_result.rs +++ b/src/common/cpp_essentials/decoder_result.rs @@ -47,12 +47,14 @@ where Self::default() } pub fn with_eci_string_builder(src: ECIStringBuilder) -> Self { - todo!() + let mut new_self = Self::default(); + new_self.content = src; + new_self } pub fn isValid(&self) -> bool { + self.content.symbology.code != 0 //return includeErrors || (_content.symbology.code != 0 && !_error); - todo!() } pub fn content(&self) -> &ECIStringBuilder { diff --git a/src/qrcode/cpp_port/decoder.rs b/src/qrcode/cpp_port/decoder.rs index 211bc17..bda6371 100644 --- a/src/qrcode/cpp_port/decoder.rs +++ b/src/qrcode/cpp_port/decoder.rs @@ -306,7 +306,7 @@ pub fn DecodeBitStream( mode = Mode::CodecModeForBits( bits.readBits(modeBitLength as usize)?, Some(version.isMicroQRCode()), - ); + )?; } match (mode) { diff --git a/src/qrcode/decoder/mode.rs b/src/qrcode/decoder/mode.rs index 06440d7..b7b2b51 100644 --- a/src/qrcode/decoder/mode.rs +++ b/src/qrcode/decoder/mode.rs @@ -123,10 +123,18 @@ impl Mode { } pub const fn get_terminator_bit_length(version: &Version) -> u8 { - todo!() + (if version.isMicroQRCode() { + version.getVersionNumber() * 2 + 1 + } else { + 4 + }) as u8 } pub const fn get_codec_mode_bits_length(version: &Version) -> u8 { - todo!() + (if version.isMicroQRCode() { + version.getVersionNumber() - 1 + } else { + 4 + }) as u8 } /** * @param bits variable number of bits encoding a QR Code data mode @@ -134,9 +142,23 @@ 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) -> Self { + pub fn CodecModeForBits(bits: u32, isMicro: Option) -> Result { let isMicro = isMicro.unwrap_or(false); - todo!() + const BITS_2_MODE_LEN: usize = 4; + + if (!isMicro) { + if ((bits >= 0x00 && bits <= 0x05) || (bits >= 0x07 && bits <= 0x09) || bits == 0x0d) { + return Mode::try_from(bits); + } + } else { + const Bits2Mode: [Mode; BITS_2_MODE_LEN] = + [Mode::NUMERIC, Mode::ALPHANUMERIC, Mode::BYTE, Mode::KANJI]; + if ((bits as usize) < BITS_2_MODE_LEN) { + return Ok(Bits2Mode[bits as usize]); + } + } + + Err(Exceptions::format_with("Invalid codec mode")) } /** @@ -162,3 +184,11 @@ impl TryFrom for Mode { Self::forBits(value) } } + +impl TryFrom for Mode { + type Error = Exceptions; + + fn try_from(value: u32) -> Result { + Self::forBits(value as u8) + } +} diff --git a/src/qrcode/decoder/version.rs b/src/qrcode/decoder/version.rs index 59c54dc..d39e360 100755 --- a/src/qrcode/decoder/version.rs +++ b/src/qrcode/decoder/version.rs @@ -57,7 +57,7 @@ pub struct Version { pub(crate) is_micro: bool, } impl Version { - fn new(versionNumber: u32, alignmentPatternCenters: Vec, ecBlocks: [ECBlocks;4]) -> Self { + fn new(versionNumber: u32, alignmentPatternCenters: Vec, ecBlocks: [ECBlocks; 4]) -> Self { let mut total = 0; let ecCodewords = ecBlocks[0].getECCodewordsPerBlock(); let ecbArray = ecBlocks[0].getECBlocks(); @@ -72,7 +72,7 @@ impl Version { alignmentPatternCenters, ecBlocks: ecBlocks.to_vec(), totalCodewords: total, - is_micro: false + is_micro: false, } } @@ -91,11 +91,11 @@ impl Version { alignmentPatternCenters: Vec::default(), ecBlocks, totalCodewords: total, - is_micro: true + is_micro: true, } } - pub fn getVersionNumber(&self) -> u32 { + pub const fn getVersionNumber(&self) -> u32 { self.versionNumber } @@ -203,19 +203,38 @@ impl Version { Ok(bitMatrix) } -pub fn build_micro_versions() -> Vec { - vec![ - Version::new_micro(1, vec![ECBlocks::new(2, vec![ECB::new(1,3)])]), - Version::new_micro(2, vec![ECBlocks::new(5, vec![ECB::new(1,5)]), ECBlocks::new(6, vec![ECB::new(1,4)])]), - Version::new_micro(3, vec![ECBlocks::new(6, vec![ECB::new(1,11)]), ECBlocks::new(8, vec![ECB::new(1,9)])]), - Version::new_micro(4, vec![ECBlocks::new(8, vec![ECB::new(1,16)]), ECBlocks::new(10, vec![ECB::new(1,14)]), ECBlocks::new(14, vec![ECB::new(1,10)])]), - ] - // static const Version allVersions[] = { - // {1, {2, 1, 3, 0, 0}}, - // {2, {5, 1, 5, 0, 0, 6, 1, 4, 0, 0}}, - // {3, {6, 1, 11, 0, 0, 8, 1, 9, 0, 0}}, - // {4, {8, 1, 16, 0, 0, 10, 1, 14, 0, 0, 14, 1, 10, 0, 0}}}; -} + pub fn build_micro_versions() -> Vec { + vec![ + Version::new_micro(1, vec![ECBlocks::new(2, vec![ECB::new(1, 3)])]), + Version::new_micro( + 2, + vec![ + ECBlocks::new(5, vec![ECB::new(1, 5)]), + ECBlocks::new(6, vec![ECB::new(1, 4)]), + ], + ), + Version::new_micro( + 3, + vec![ + ECBlocks::new(6, vec![ECB::new(1, 11)]), + ECBlocks::new(8, vec![ECB::new(1, 9)]), + ], + ), + Version::new_micro( + 4, + vec![ + ECBlocks::new(8, vec![ECB::new(1, 16)]), + ECBlocks::new(10, vec![ECB::new(1, 14)]), + ECBlocks::new(14, vec![ECB::new(1, 10)]), + ], + ), + ] + // static const Version allVersions[] = { + // {1, {2, 1, 3, 0, 0}}, + // {2, {5, 1, 5, 0, 0, 6, 1, 4, 0, 0}}, + // {3, {6, 1, 11, 0, 0, 8, 1, 9, 0, 0}}, + // {4, {8, 1, 16, 0, 0, 10, 1, 14, 0, 0, 14, 1, 10, 0, 0}}}; + } /** * See ISO 18004:2006 6.5.1 Table 9 From 517b23b48111e5281d43d5258bfc2320c57ec0c0 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 28 Mar 2023 16:46:36 -0500 Subject: [PATCH 26/72] more todo!() removal --- .../base_extentions/qrcode_version.rs | 10 +++++ src/qrcode/cpp_port/bitmatrix_parser.rs | 40 +++++++++++-------- src/qrcode/cpp_port/detector.rs | 2 +- src/qrcode/decoder/mode.rs | 29 +++++++++++++- 4 files changed, 63 insertions(+), 18 deletions(-) diff --git a/src/common/cpp_essentials/base_extentions/qrcode_version.rs b/src/common/cpp_essentials/base_extentions/qrcode_version.rs index e0415a3..a61fb44 100644 --- a/src/common/cpp_essentials/base_extentions/qrcode_version.rs +++ b/src/common/cpp_essentials/base_extentions/qrcode_version.rs @@ -16,6 +16,16 @@ use crate::Exceptions; // } impl Version { + pub fn FromDimension(dimension: u32) -> Result { + todo!() + // bool isMicro = dimension < 21; + // if (dimension % DimensionStep(isMicro) != 1) { + // //throw std::invalid_argument("Unexpected dimension"); + // return nullptr; + // } + // return FromNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro); + } + pub fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 { Self::DimensionOffset(is_micro) + Self::DimensionStep(is_micro) * version } diff --git a/src/qrcode/cpp_port/bitmatrix_parser.rs b/src/qrcode/cpp_port/bitmatrix_parser.rs index 40a98e8..3c33ac8 100644 --- a/src/qrcode/cpp_port/bitmatrix_parser.rs +++ b/src/qrcode/cpp_port/bitmatrix_parser.rs @@ -10,6 +10,8 @@ use crate::{ Exceptions, }; +use super::detector::AppendBit; + pub fn getBit(bitMatrix: &BitMatrix, x: u32, y: u32, mirrored: Option) -> bool { let mirrored = mirrored.unwrap_or(false); if mirrored { @@ -29,27 +31,33 @@ pub fn hasValidDimension(bitMatrix: &BitMatrix, isMicro: bool) -> bool { } pub fn ReadVersion(bitMatrix: &BitMatrix) -> Result { - todo!() - // int dimension = bitMatrix.height(); + let dimension = bitMatrix.height(); - // const Version* version = Version::FromDimension(dimension); + let mut version = Version::FromDimension(dimension)?; - // if (!version || version->versionNumber() < 7) - // return version; + if (version.getVersionNumber() < 7) { + return Ok(version); + } - // for (bool mirror : {false, true}) { - // // Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored) - // int versionBits = 0; - // for (int y = 5; y >= 0; --y) - // for (int x = dimension - 9; x >= dimension - 11; --x) - // AppendBit(versionBits, getBit(bitMatrix, x, y, mirror)); + for mirror in [false, true] { + // for (bool mirror : {false, true}) { + // Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored) + let mut versionBits = 0; + for y in (0..=5).rev() { + // for (int y = 5; y >= 0; --y) { + for x in ((dimension - 11)..=(dimension - 9)).rev() { + // for (int x = dimension - 9; x >= dimension - 11; --x) { + AppendBit(&mut versionBits, getBit(bitMatrix, x, y, Some(mirror))); + } + } - // version = Version::DecodeVersionInformation(versionBits); - // if (version && version->dimension() == dimension) - // return version; - // } + version = Version::DecodeVersionInformation(versionBits, 0)?; // THIS MIGHT BE WRONG todo!() + if (version.getDimensionForVersion() == dimension) { + return Ok(version); + } + } - // return nullptr; + return Err(Exceptions::FORMAT); } pub fn ReadFormatInformation(bitMatrix: &BitMatrix, isMicro: bool) -> Result { diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index d95fc64..ac34c16 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -448,7 +448,7 @@ pub fn ReadVersion( Version::DecodeVersionInformation(bits[0], bits[1]) } -fn AppendBit(val: &mut i32, bit: bool) { +pub fn AppendBit(val: &mut i32, bit: bool) { *val <<= 1; *val |= i32::from(bit) diff --git a/src/qrcode/decoder/mode.rs b/src/qrcode/decoder/mode.rs index b7b2b51..47e97fc 100644 --- a/src/qrcode/decoder/mode.rs +++ b/src/qrcode/decoder/mode.rs @@ -167,7 +167,34 @@ impl Mode { * count of characters that will follow encoded in this Mode */ pub fn CharacterCountBits(&self, version: &Version) -> u32 { - todo!() + let number = version.getVersionNumber() as usize; + if (version.isMicroQRCode()) { + match (self) { + Mode::NUMERIC=> return [3, 4, 5, 6][number - 1], + Mode::ALPHANUMERIC=> return [3, 4, 5][number - 2], + Mode::BYTE=> return [4, 5][number - 3], + Mode::KANJI | //=> [[fallthrough]], + Mode::HANZI=> return [3, 4][number - 3], + _=> return 0, + } + } + + let i = if (number <= 9) { + 0 + } else if (number <= 26) { + 1 + } else { + 2 + }; + + match (self) { + Mode::NUMERIC=> return [10, 12, 14][i], + Mode::ALPHANUMERIC=> return [9, 11, 13][i], + Mode::BYTE=> return [8, 16, 16][i], + Mode::KANJI| // [[fallthrough]]; + Mode::HANZI=> return [8, 10, 12][i], + _=> return 0, + } } } From 4ccfeddf4e598d3b50aa9099b21b3ba3317185fc Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 28 Mar 2023 18:19:33 -0500 Subject: [PATCH 27/72] clap version bump --- crates/cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 45e3d1f..07670df 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -10,5 +10,5 @@ keywords = ["barcode", "2d_barcode", "1d_barcode", "barcode_reader", "barcode_wr # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = { version = "4.1.1", features = ["derive"] } +clap = { version = "4.2.0", features = ["derive"] } rxing = {path = "../../", version = "~0.4.0", features = ["image", "svg_read", "svg_write"] } From 6a1c5c568a0f744eb7d80992e8ab276bf8faf566 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Wed, 29 Mar 2023 12:10:58 -0500 Subject: [PATCH 28/72] continued removal of todo!() --- .../base_extentions/qr_formatinformation.rs | 25 +++++- src/qrcode/cpp_port/bitmatrix_parser.rs | 81 +++++++++++-------- 2 files changed, 71 insertions(+), 35 deletions(-) diff --git a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs index 149c838..720abcb 100644 --- a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs +++ b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs @@ -1,8 +1,28 @@ use crate::qrcode::decoder::FormatInformation; impl FormatInformation { + /** + * @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 DecodeQR(formatInfoBits1: u32, formatInfoBits2: u32) -> Self { + todo!() + // // maks out the 'Dark Module' for mirrored and non-mirrored case (see Figure 25 in ISO/IEC 18004:2015) + // uint32_t mirroredFormatInfoBits2 = MirrorBits(((formatInfoBits2 >> 1) & 0b111111110000000) | (formatInfoBits2 & 0b1111111)); + // formatInfoBits2 = ((formatInfoBits2 >> 1) & 0b111111100000000) | (formatInfoBits2 & 0b11111111); + // auto fi = FindBestFormatInfo(FORMAT_INFO_MASK_QR, FORMAT_INFO_DECODE_LOOKUP, + // {formatInfoBits1, formatInfoBits2, MirrorBits(formatInfoBits1), mirroredFormatInfoBits2}); + + // // Use bits 3/4 for error correction, and 0-2 for mask. + // fi.ecLevel = ECLevelFromBits((fi.index >> 3) & 0x03); + // fi.dataMask = static_cast(fi.index & 0x07); + // fi.isMirrored = fi.bitsIndex > 1; + + // return fi; + } + pub fn DecodeMQR(formatInfoBits: u32) -> Self { - unimplemented!() + todo!() // // We don't use the additional masking (with 0x4445) to work around potentially non complying MicroQRCode encoders // auto fi = FindBestFormatInfo(0, FORMAT_INFO_DECODE_LOOKUP_MICRO, {formatInfoBits, MirrorBits(formatInfoBits)}); @@ -16,7 +36,8 @@ impl FormatInformation { // return fi; } + pub fn isValid(&self) -> bool { - unimplemented!() + todo!() } } diff --git a/src/qrcode/cpp_port/bitmatrix_parser.rs b/src/qrcode/cpp_port/bitmatrix_parser.rs index 3c33ac8..2eafb77 100644 --- a/src/qrcode/cpp_port/bitmatrix_parser.rs +++ b/src/qrcode/cpp_port/bitmatrix_parser.rs @@ -61,44 +61,59 @@ pub fn ReadVersion(bitMatrix: &BitMatrix) -> Result { } pub fn ReadFormatInformation(bitMatrix: &BitMatrix, isMicro: bool) -> Result { - todo!() - // if (!hasValidDimension(bitMatrix, isMicro)) - // return {}; + if (!hasValidDimension(bitMatrix, isMicro)) { + return Err(Exceptions::FORMAT); + } - // if (isMicro) { - // // Read top-left format info bits - // int formatInfoBits = 0; - // for (int x = 1; x < 9; x++) - // AppendBit(formatInfoBits, getBit(bitMatrix, x, 8)); - // for (int y = 7; y >= 1; y--) - // AppendBit(formatInfoBits, getBit(bitMatrix, 8, y)); + if (isMicro) { + // Read top-left format info bits + let mut formatInfoBits = 0; + for x in 1..9 { + // for (int x = 1; x < 9; x++) + AppendBit(&mut formatInfoBits, getBit(bitMatrix, x, 8, None)); + } + for y in (1..=7).rev() { + // for (int y = 7; y >= 1; y--) + AppendBit(&mut formatInfoBits, getBit(bitMatrix, 8, y, None)); + } - // return FormatInformation::DecodeMQR(formatInfoBits); - // } + return Ok(FormatInformation::DecodeMQR(formatInfoBits as u32)); + } - // // Read top-left format info bits - // int formatInfoBits1 = 0; - // for (int x = 0; x < 6; x++) - // AppendBit(formatInfoBits1, getBit(bitMatrix, x, 8)); - // // .. and skip a bit in the timing pattern ... - // AppendBit(formatInfoBits1, getBit(bitMatrix, 7, 8)); - // AppendBit(formatInfoBits1, getBit(bitMatrix, 8, 8)); - // AppendBit(formatInfoBits1, getBit(bitMatrix, 8, 7)); - // // .. and skip a bit in the timing pattern ... - // for (int y = 5; y >= 0; y--) - // AppendBit(formatInfoBits1, getBit(bitMatrix, 8, y)); + // Read top-left format info bits + let mut formatInfoBits1 = 0; + for x in 0..6 { + // for (int x = 0; x < 6; x++) + AppendBit(&mut formatInfoBits1, getBit(bitMatrix, x, 8, None)); + } + // .. and skip a bit in the timing pattern ... + AppendBit(&mut formatInfoBits1, getBit(bitMatrix, 7, 8, None)); + AppendBit(&mut formatInfoBits1, getBit(bitMatrix, 8, 8, None)); + AppendBit(&mut formatInfoBits1, getBit(bitMatrix, 8, 7, None)); + // .. and skip a bit in the timing pattern ... + for y in (0..=6).rev() { + // for (int y = 5; y >= 0; y--) + AppendBit(&mut formatInfoBits1, getBit(bitMatrix, 8, y, None)); + } - // // Read the top-right/bottom-left pattern including the 'Dark Module' from the bottom-left - // // part that has to be considered separately when looking for mirrored symbols. - // // See also FormatInformation::DecodeQR - // int dimension = bitMatrix.height(); - // int formatInfoBits2 = 0; - // for (int y = dimension - 1; y >= dimension - 8; y--) - // AppendBit(formatInfoBits2, getBit(bitMatrix, 8, y)); - // for (int x = dimension - 8; x < dimension; x++) - // AppendBit(formatInfoBits2, getBit(bitMatrix, x, 8)); + // Read the top-right/bottom-left pattern including the 'Dark Module' from the bottom-left + // part that has to be considered separately when looking for mirrored symbols. + // See also FormatInformation::DecodeQR + let dimension = bitMatrix.height(); + let mut formatInfoBits2 = 0; + for y in ((dimension - 8)..=(dimension - 1)).rev() { + // for (int y = dimension - 1; y >= dimension - 8; y--) + AppendBit(&mut formatInfoBits2, getBit(bitMatrix, 8, y, None)); + } + for x in (dimension - 8)..dimension { + // for (int x = dimension - 8; x < dimension; x++) + AppendBit(&mut formatInfoBits2, getBit(bitMatrix, x, 8, None)); + } - // return FormatInformation::DecodeQR(formatInfoBits1, formatInfoBits2); + return Ok(FormatInformation::DecodeQR( + formatInfoBits1 as u32, + formatInfoBits2 as u32, + )); } pub fn ReadQRCodewords( From af626afc151902080178930dd292f1eb46e77500 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Wed, 29 Mar 2023 14:30:19 -0500 Subject: [PATCH 29/72] removed todo!() from qrcode_version.rs --- .../base_extentions/qr_formatinformation.rs | 6 ++-- .../base_extentions/qrcode_version.rs | 31 ++++++++++++++----- src/qrcode/decoder/version.rs | 4 +-- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs index 720abcb..414cfa2 100644 --- a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs +++ b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs @@ -2,9 +2,9 @@ use crate::qrcode::decoder::FormatInformation; impl FormatInformation { /** - * @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 - */ + * @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 DecodeQR(formatInfoBits1: u32, formatInfoBits2: u32) -> Self { todo!() // // maks out the 'Dark Module' for mirrored and non-mirrored case (see Figure 25 in ISO/IEC 18004:2015) diff --git a/src/common/cpp_essentials/base_extentions/qrcode_version.rs b/src/common/cpp_essentials/base_extentions/qrcode_version.rs index a61fb44..4e1ba46 100644 --- a/src/common/cpp_essentials/base_extentions/qrcode_version.rs +++ b/src/common/cpp_essentials/base_extentions/qrcode_version.rs @@ -1,5 +1,5 @@ use crate::common::Result; -use crate::qrcode::decoder::{Version, VersionRef, VERSION_DECODE_INFO}; +use crate::qrcode::decoder::{Version, VersionRef, MICRO_VERSIONS, VERSIONS, VERSION_DECODE_INFO}; use crate::Exceptions; // const Version* Version::AllMicroVersions() @@ -17,13 +17,28 @@ use crate::Exceptions; impl Version { pub fn FromDimension(dimension: u32) -> Result { - todo!() - // bool isMicro = dimension < 21; - // if (dimension % DimensionStep(isMicro) != 1) { - // //throw std::invalid_argument("Unexpected dimension"); - // return nullptr; - // } - // return FromNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro); + let isMicro = dimension < 21; + if (dimension % Self::DimensionStep(isMicro) != 1) { + //throw std::invalid_argument("Unexpected dimension"); + return Err(Exceptions::ILLEGAL_ARGUMENT); + } + return Self::FromNumber( + (dimension - Self::DimensionOffset(isMicro)) / Self::DimensionStep(isMicro), + isMicro, + ); + } + + pub fn FromNumber(versionNumber: u32, is_micro: bool) -> Result { + if (versionNumber < 1 || versionNumber > (if is_micro { 4 } else { 40 })) { + //throw std::invalid_argument("Version should be in range [1-40]."); + return Err(Exceptions::ILLEGAL_ARGUMENT); + } + + Ok(if is_micro { + &MICRO_VERSIONS[versionNumber as usize - 1] + } else { + &VERSIONS[versionNumber as usize - 1] + }) } pub fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 { diff --git a/src/qrcode/decoder/version.rs b/src/qrcode/decoder/version.rs index d39e360..98a1ab2 100755 --- a/src/qrcode/decoder/version.rs +++ b/src/qrcode/decoder/version.rs @@ -27,8 +27,8 @@ use once_cell::sync::Lazy; pub type VersionRef = &'static Version; -static VERSIONS: Lazy> = Lazy::new(Version::buildVersions); -static MICRO_VERSIONS: Lazy> = Lazy::new(Version::build_micro_versions); +pub static VERSIONS: Lazy> = Lazy::new(Version::buildVersions); +pub static MICRO_VERSIONS: Lazy> = Lazy::new(Version::build_micro_versions); /** * See ISO 18004:2006 Annex D. From ae5539e2701ae73ff67390c9d76bf753b2cd8d18 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 30 Mar 2023 10:32:42 -0500 Subject: [PATCH 30/72] add ReadQRCodewords --- .../base_extentions/qr_formatinformation.rs | 26 +++++- src/qrcode/cpp_port/bitmatrix_parser.rs | 82 +++++++++++-------- src/qrcode/cpp_port/data_mask.rs | 47 +++++++++++ src/qrcode/cpp_port/mod.rs | 1 + 4 files changed, 120 insertions(+), 36 deletions(-) create mode 100644 src/qrcode/cpp_port/data_mask.rs diff --git a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs index 414cfa2..15001e6 100644 --- a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs +++ b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs @@ -24,7 +24,7 @@ impl FormatInformation { pub fn DecodeMQR(formatInfoBits: u32) -> Self { todo!() // // We don't use the additional masking (with 0x4445) to work around potentially non complying MicroQRCode encoders - // auto fi = FindBestFormatInfo(0, FORMAT_INFO_DECODE_LOOKUP_MICRO, {formatInfoBits, MirrorBits(formatInfoBits)}); + // let fi = FindBestFormatInfo(0, FORMAT_INFO_DECODE_LOOKUP_MICRO, {formatInfoBits, MirrorBits(formatInfoBits)}); // constexpr uint8_t BITS_TO_VERSION[] = {1, 2, 2, 3, 3, 4, 4, 4}; @@ -37,6 +37,30 @@ impl FormatInformation { // return fi; } + pub fn MirrorBits(bits: u32) -> u32 { + todo!() + // return BitHacks::Reverse(bits) >> 17; + } + + pub fn FindBestFormatInfo(mask: u32, lookup: [(u32, u32); 32], bits: &[u32]) -> Self { + todo!() + // FormatInformation fi; + + // // Some QR codes apparently do not apply the XOR mask. Try without and with additional masking. + // for (auto mask : {0, mask}) + // for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex) + // for (const auto& [pattern, index] : lookup) { + // // Find the int in lookup with fewest bits differing + // if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); hammingDist < fi.hammingDistance) { + // fi.index = index; + // fi.hammingDistance = hammingDist; + // fi.bitsIndex = bitsIndex; + // } + // } + + // return fi; + } + pub fn isValid(&self) -> bool { todo!() } diff --git a/src/qrcode/cpp_port/bitmatrix_parser.rs b/src/qrcode/cpp_port/bitmatrix_parser.rs index 2eafb77..275c75d 100644 --- a/src/qrcode/cpp_port/bitmatrix_parser.rs +++ b/src/qrcode/cpp_port/bitmatrix_parser.rs @@ -10,7 +10,7 @@ use crate::{ Exceptions, }; -use super::detector::AppendBit; +use super::{data_mask::GetDataMaskBit, detector::AppendBit}; pub fn getBit(bitMatrix: &BitMatrix, x: u32, y: u32, mirrored: Option) -> bool { let mirrored = mirrored.unwrap_or(false); @@ -121,42 +121,54 @@ pub fn ReadQRCodewords( version: VersionRef, formatInfo: &FormatInformation, ) -> Result> { - todo!() - // BitMatrix functionPattern = version.buildFunctionPattern(); + let functionPattern: BitMatrix = version.buildFunctionPattern()?; - // ByteArray result; - // result.reserve(version.totalCodewords()); - // uint8_t currentByte = 0; - // bool readingUp = true; - // int bitsRead = 0; - // int dimension = bitMatrix.height(); - // // Read columns in pairs, from right to left - // for (int x = dimension - 1; x > 0; x -= 2) { - // // Skip whole column with vertical timing pattern. - // if (x == 6) - // x--; - // // Read alternatingly from bottom to top then top to bottom - // for (int row = 0; row < dimension; row++) { - // int y = readingUp ? dimension - 1 - row : row; - // for (int col = 0; col < 2; col++) { - // int xx = x - col; - // // Ignore bits covered by the function pattern - // if (!functionPattern.get(xx, y)) { - // // Read a bit - // AppendBit(currentByte, - // GetDataMaskBit(formatInfo.dataMask, xx, y) != getBit(bitMatrix, xx, y, formatInfo.isMirrored)); - // // If we've made a whole byte, save it off - // if (++bitsRead % 8 == 0) - // result.push_back(std::exchange(currentByte, 0)); - // } - // } - // } - // readingUp = !readingUp; // switch directions - // } - // if (Size(result) != version.totalCodewords()) - // return {}; + let mut result = Vec::new(); + result.reserve(version.getTotalCodewords() as usize); + let mut currentByte = 0; + let mut readingUp = true; + let mut bitsRead = 0; + let dimension = bitMatrix.height(); + // Read columns in pairs, from right to left + let mut x = dimension - 1; + while x > 0 { + // for (int x = dimension - 1; x > 0; x -= 2) { + // Skip whole column with vertical timing pattern. + if (x == 6) { + x -= 1; + } + // Read alternatingly from bottom to top then top to bottom + for row in 0..dimension { + // for (int row = 0; row < dimension; row++) { + let y = if readingUp { dimension - 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, y)) { + // Read a bit + AppendBit( + &mut currentByte, + GetDataMaskBit(formatInfo.data_mask as u32, xx, y, None) + != getBit(bitMatrix, xx, y, Some(formatInfo.isMirrored)), + ); + // If we've made a whole byte, save it off + bitsRead += 1; + if (bitsRead % 8 == 0) { + result.push(std::mem::take(&mut currentByte)); + } + } + } + } + readingUp = !readingUp; // switch directions - // return result; + 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 ReadMQRCodewords( diff --git a/src/qrcode/cpp_port/data_mask.rs b/src/qrcode/cpp_port/data_mask.rs new file mode 100644 index 0000000..1db71c3 --- /dev/null +++ b/src/qrcode/cpp_port/data_mask.rs @@ -0,0 +1,47 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::BitMatrix; + +/** +*

Encapsulates data masks for the data bits in a QR and micro QR code, per ISO 18004:2006 6.8.

+* +*

Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position +* and j is row position. In fact, as the text says, i is row position and j is column position.

+*/ + +pub fn GetDataMaskBit(maskIndex: u32, x: u32, y: u32, isMicro: Option) -> bool { + let isMicro = isMicro.unwrap_or(false); + todo!() + // if (isMicro) { + // if (maskIndex < 0 || maskIndex >= 4) + // throw std::invalid_argument("QRCode maskIndex out of range"); + // maskIndex = std::array{1, 4, 6, 7}[maskIndex]; // map from MQR to QR indices + // } + + // switch (maskIndex) { + // case 0: return (y + x) % 2 == 0; + // case 1: return y % 2 == 0; + // case 2: return x % 3 == 0; + // case 3: return (y + x) % 3 == 0; + // case 4: return ((y / 2) + (x / 3)) % 2 == 0; + // case 5: return (y * x) % 6 == 0; + // case 6: return ((y * x) % 6) < 3; + // case 7: return (y + x + ((y * x) % 3)) % 2 == 0; + // } + + // throw std::invalid_argument("QRCode maskIndex out of range"); +} + +pub fn GetMaskedBit( + bits: &BitMatrix, + x: u32, + y: u32, + maskIndex: u32, + isMicro: Option, +) -> bool { + return GetDataMaskBit(maskIndex, x, y, isMicro) != bits.get(x, y); +} diff --git a/src/qrcode/cpp_port/mod.rs b/src/qrcode/cpp_port/mod.rs index 464babf..2893c1b 100644 --- a/src/qrcode/cpp_port/mod.rs +++ b/src/qrcode/cpp_port/mod.rs @@ -1,3 +1,4 @@ +mod data_mask; pub mod decoder; pub mod detector; From 71ae1e96a18ff99eea32e85061ac8c6e1e06031c Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 30 Mar 2023 10:41:19 -0500 Subject: [PATCH 31/72] add ReadMQRCodewords --- src/qrcode/cpp_port/bitmatrix_parser.rs | 100 ++++++++++++++---------- 1 file changed, 59 insertions(+), 41 deletions(-) diff --git a/src/qrcode/cpp_port/bitmatrix_parser.rs b/src/qrcode/cpp_port/bitmatrix_parser.rs index 275c75d..b70c978 100644 --- a/src/qrcode/cpp_port/bitmatrix_parser.rs +++ b/src/qrcode/cpp_port/bitmatrix_parser.rs @@ -6,7 +6,7 @@ use crate::{ common::{BitMatrix, Result}, - qrcode::decoder::{FormatInformation, Version, VersionRef}, + qrcode::decoder::{ErrorCorrectionLevel, FormatInformation, Version, VersionRef}, Exceptions, }; @@ -176,49 +176,67 @@ pub fn ReadMQRCodewords( version: VersionRef, formatInfo: &FormatInformation, ) -> Result> { - todo!() - // BitMatrix functionPattern = version.buildFunctionPattern(); + let functionPattern = version.buildFunctionPattern()?; - // // D3 in a Version M1 symbol, D11 in a Version M3-L symbol and D9 - // // in a Version M3-M symbol is a 2x2 square 4-module block. - // // See ISO 18004:2006 6.7.3. - // bool hasD4mBlock = version.versionNumber() % 2 == 1; - // int d4mBlockIndex = - // version.versionNumber() == 1 ? 3 : (formatInfo.ecLevel == QRCode::ErrorCorrectionLevel::Low ? 11 : 9); + // D3 in a Version M1 symbol, D11 in a Version M3-L symbol and D9 + // in a Version M3-M symbol is a 2x2 square 4-module block. + // See ISO 18004:2006 6.7.3. + let hasD4mBlock = version.getVersionNumber() % 2 == 1; + let d4mBlockIndex = if version.getVersionNumber() == 1 { + 3 + } else { + (if formatInfo.error_correction_level == ErrorCorrectionLevel::L { + 11 + } else { + 9 + }) + }; - // ByteArray result; - // result.reserve(version.totalCodewords()); - // uint8_t currentByte = 0; - // bool readingUp = true; - // int bitsRead = 0; - // int dimension = bitMatrix.height(); - // // Read columns in pairs, from right to left - // for (int x = dimension - 1; x > 0; x -= 2) { - // // Read alternatingly from bottom to top then top to bottom - // for (int row = 0; row < dimension; row++) { - // int y = readingUp ? dimension - 1 - row : row; - // for (int col = 0; col < 2; col++) { - // int xx = x - col; - // // Ignore bits covered by the function pattern - // if (!functionPattern.get(xx, y)) { - // // Read a bit - // AppendBit(currentByte, - // GetDataMaskBit(formatInfo.dataMask, xx, y, true) != getBit(bitMatrix, xx, y, formatInfo.isMirrored)); - // ++bitsRead; - // // If we've made a whole byte, save it off; save early if 2x2 data block. - // if (bitsRead == 8 || (bitsRead == 4 && hasD4mBlock && Size(result) == d4mBlockIndex - 1)) { - // result.push_back(std::exchange(currentByte, 0)); - // bitsRead = 0; - // } - // } - // } - // } - // readingUp = !readingUp; // switch directions - // } - // if (Size(result) != version.totalCodewords()) - // return {}; + let mut result = Vec::new(); + result.reserve(version.getTotalCodewords() as usize); + let mut currentByte = 0; + let mut readingUp = true; + let mut bitsRead = 0; + let dimension = bitMatrix.height(); + // Read columns in pairs, from right to left + let mut x = dimension - 1; + while x > 0 { + // for (int x = dimension - 1; x > 0; x -= 2) { + // Read alternatingly from bottom to top then top to bottom + for row in 0..dimension { + // for (int row = 0; row < dimension; row++) { + let y = if readingUp { dimension - 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, y)) { + // Read a bit + AppendBit( + &mut currentByte, + GetDataMaskBit(formatInfo.data_mask as u32, xx, y, Some(true)) + != getBit(bitMatrix, xx, y, Some(formatInfo.isMirrored)), + ); + bitsRead += 1; + // If we've made a whole byte, save it off; save early if 2x2 data block. + if (bitsRead == 8 + || (bitsRead == 4 && hasD4mBlock && (result.len()) == d4mBlockIndex - 1)) + { + result.push(std::mem::take(&mut currentByte)); + bitsRead = 0; + } + } + } + } + readingUp = !readingUp; // switch directions - // return result; + 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( From 7f675137f2be31a642eb179e1a01d8df83f639fe Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 30 Mar 2023 11:04:45 -0500 Subject: [PATCH 32/72] data_mask.rs added --- src/qrcode/cpp_port/bitmatrix_parser.rs | 4 +-- src/qrcode/cpp_port/data_mask.rs | 48 ++++++++++++++----------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/qrcode/cpp_port/bitmatrix_parser.rs b/src/qrcode/cpp_port/bitmatrix_parser.rs index b70c978..d629043 100644 --- a/src/qrcode/cpp_port/bitmatrix_parser.rs +++ b/src/qrcode/cpp_port/bitmatrix_parser.rs @@ -149,7 +149,7 @@ pub fn ReadQRCodewords( // Read a bit AppendBit( &mut currentByte, - GetDataMaskBit(formatInfo.data_mask as u32, xx, y, None) + GetDataMaskBit(formatInfo.data_mask as u32, xx, y, None)? != getBit(bitMatrix, xx, y, Some(formatInfo.isMirrored)), ); // If we've made a whole byte, save it off @@ -214,7 +214,7 @@ pub fn ReadMQRCodewords( // Read a bit AppendBit( &mut currentByte, - GetDataMaskBit(formatInfo.data_mask as u32, xx, y, Some(true)) + GetDataMaskBit(formatInfo.data_mask as u32, xx, y, Some(true))? != getBit(bitMatrix, xx, y, Some(formatInfo.isMirrored)), ); bitsRead += 1; diff --git a/src/qrcode/cpp_port/data_mask.rs b/src/qrcode/cpp_port/data_mask.rs index 1db71c3..6effb85 100644 --- a/src/qrcode/cpp_port/data_mask.rs +++ b/src/qrcode/cpp_port/data_mask.rs @@ -5,6 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::common::BitMatrix; +use crate::common::Result; +use crate::Exceptions; /** *

Encapsulates data masks for the data bits in a QR and micro QR code, per ISO 18004:2006 6.8.

@@ -13,27 +15,33 @@ use crate::common::BitMatrix; * and j is row position. In fact, as the text says, i is row position and j is column position.

*/ -pub fn GetDataMaskBit(maskIndex: u32, x: u32, y: u32, isMicro: Option) -> bool { +pub fn GetDataMaskBit(maskIndex: u32, x: u32, y: u32, isMicro: Option) -> Result { let isMicro = isMicro.unwrap_or(false); - todo!() - // if (isMicro) { - // if (maskIndex < 0 || maskIndex >= 4) - // throw std::invalid_argument("QRCode maskIndex out of range"); - // maskIndex = std::array{1, 4, 6, 7}[maskIndex]; // map from MQR to QR indices - // } + let mut maskIndex = maskIndex; + if (isMicro) { + if (maskIndex < 0 || maskIndex >= 4) { + return Err(Exceptions::illegal_argument_with( + "QRCode maskIndex out of range", + )); + } + maskIndex = [1, 4, 6, 7][maskIndex as usize]; // map from MQR to QR indices + } - // switch (maskIndex) { - // case 0: return (y + x) % 2 == 0; - // case 1: return y % 2 == 0; - // case 2: return x % 3 == 0; - // case 3: return (y + x) % 3 == 0; - // case 4: return ((y / 2) + (x / 3)) % 2 == 0; - // case 5: return (y * x) % 6 == 0; - // case 6: return ((y * x) % 6) < 3; - // case 7: return (y + x + ((y * x) % 3)) % 2 == 0; - // } + match (maskIndex) { + 0 => return Ok((y + x) % 2 == 0), + 1 => return Ok(y % 2 == 0), + 2 => return Ok(x % 3 == 0), + 3 => return Ok((y + x) % 3 == 0), + 4 => return Ok(((y / 2) + (x / 3)) % 2 == 0), + 5 => return Ok((y * x) % 6 == 0), + 6 => return Ok(((y * x) % 6) < 3), + 7 => return Ok((y + x + ((y * x) % 3)) % 2 == 0), + _ => {} + } - // throw std::invalid_argument("QRCode maskIndex out of range"); + Err(Exceptions::illegal_argument_with( + "QRCode maskIndex out of range", + )) } pub fn GetMaskedBit( @@ -42,6 +50,6 @@ pub fn GetMaskedBit( y: u32, maskIndex: u32, isMicro: Option, -) -> bool { - return GetDataMaskBit(maskIndex, x, y, isMicro) != bits.get(x, y); +) -> Result { + Ok(GetDataMaskBit(maskIndex, x, y, isMicro)? != bits.get(x, y)) } From 98e30a1d801f2e9899f0cc8b6ac373981284fb2a Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 30 Mar 2023 15:01:31 -0500 Subject: [PATCH 33/72] porting formatinformation and eclevel --- .../cpp_essentials/base_extentions/mod.rs | 1 + .../base_extentions/qr_ec_level.rs | 27 +++++ .../base_extentions/qr_formatinformation.rs | 100 ++++++++++++++---- src/qrcode/decoder/format_information.rs | 11 +- 4 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 src/common/cpp_essentials/base_extentions/qr_ec_level.rs diff --git a/src/common/cpp_essentials/base_extentions/mod.rs b/src/common/cpp_essentials/base_extentions/mod.rs index 06e69f7..5c34e18 100644 --- a/src/common/cpp_essentials/base_extentions/mod.rs +++ b/src/common/cpp_essentials/base_extentions/mod.rs @@ -1,3 +1,4 @@ mod bitmatrix; +mod qr_ec_level; mod qr_formatinformation; mod qrcode_version; diff --git a/src/common/cpp_essentials/base_extentions/qr_ec_level.rs b/src/common/cpp_essentials/base_extentions/qr_ec_level.rs new file mode 100644 index 0000000..365c7d3 --- /dev/null +++ b/src/common/cpp_essentials/base_extentions/qr_ec_level.rs @@ -0,0 +1,27 @@ +use crate::common::Result; +use crate::qrcode::decoder::ErrorCorrectionLevel; + +impl ErrorCorrectionLevel { + pub fn ECLevelFromBits(bits: u8, isMicro: bool) -> Self { + if (isMicro) { + let LEVEL_FOR_BITS: [ErrorCorrectionLevel; 8] = [ + ErrorCorrectionLevel::L, + ErrorCorrectionLevel::L, + ErrorCorrectionLevel::M, + ErrorCorrectionLevel::L, + ErrorCorrectionLevel::M, + ErrorCorrectionLevel::L, + ErrorCorrectionLevel::M, + ErrorCorrectionLevel::Q, + ]; + return LEVEL_FOR_BITS[bits as usize & 0x07]; + } + let LEVEL_FOR_BITS: [ErrorCorrectionLevel; 4] = [ + ErrorCorrectionLevel::M, + ErrorCorrectionLevel::L, + ErrorCorrectionLevel::H, + ErrorCorrectionLevel::Q, + ]; + return LEVEL_FOR_BITS[bits as usize & 0x3]; + } +} diff --git a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs index 15001e6..ebd87e6 100644 --- a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs +++ b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs @@ -1,4 +1,43 @@ -use crate::qrcode::decoder::FormatInformation; +use crate::common::Result; + +use crate::qrcode::decoder::{ + ErrorCorrectionLevel, FormatInformation, FORMAT_INFO_DECODE_LOOKUP, FORMAT_INFO_MASK_QR, +}; + +pub const FORMAT_INFO_DECODE_LOOKUP_MICRO: [[u32; 2]; 32] = [ + [0x4445, 0x00], + [0x4172, 0x01], + [0x4E2B, 0x02], + [0x4B1C, 0x03], + [0x55AE, 0x04], + [0x5099, 0x05], + [0x5FC0, 0x06], + [0x5AF7, 0x07], + [0x6793, 0x08], + [0x62A4, 0x09], + [0x6DFD, 0x0A], + [0x68CA, 0x0B], + [0x7678, 0x0C], + [0x734F, 0x0D], + [0x7C16, 0x0E], + [0x7921, 0x0F], + [0x06DE, 0x10], + [0x03E9, 0x11], + [0x0CB0, 0x12], + [0x0987, 0x13], + [0x1735, 0x14], + [0x1202, 0x15], + [0x1D5B, 0x16], + [0x186C, 0x17], + [0x2508, 0x18], + [0x203F, 0x19], + [0x2F66, 0x1A], + [0x2A51, 0x1B], + [0x34E3, 0x1C], + [0x31D4, 0x1D], + [0x3E8D, 0x1E], + [0x3BBA, 0x1F], +]; impl FormatInformation { /** @@ -6,35 +45,50 @@ impl FormatInformation { * @param formatInfoBits2 second copy of same info; both are checked at the same time to establish best match */ pub fn DecodeQR(formatInfoBits1: u32, formatInfoBits2: u32) -> Self { - todo!() - // // maks out the 'Dark Module' for mirrored and non-mirrored case (see Figure 25 in ISO/IEC 18004:2015) - // uint32_t mirroredFormatInfoBits2 = MirrorBits(((formatInfoBits2 >> 1) & 0b111111110000000) | (formatInfoBits2 & 0b1111111)); - // formatInfoBits2 = ((formatInfoBits2 >> 1) & 0b111111100000000) | (formatInfoBits2 & 0b11111111); - // auto fi = FindBestFormatInfo(FORMAT_INFO_MASK_QR, FORMAT_INFO_DECODE_LOOKUP, - // {formatInfoBits1, formatInfoBits2, MirrorBits(formatInfoBits1), mirroredFormatInfoBits2}); + // maks out the 'Dark Module' for mirrored and non-mirrored case (see Figure 25 in ISO/IEC 18004:2015) + let mirroredFormatInfoBits2 = Self::MirrorBits( + ((formatInfoBits2 >> 1) & 0b111111110000000) | (formatInfoBits2 & 0b1111111), + ); + let formatInfoBits2 = + ((formatInfoBits2 >> 1) & 0b111111100000000) | (formatInfoBits2 & 0b11111111); + let mut fi = Self::FindBestFormatInfo( + FORMAT_INFO_MASK_QR, + FORMAT_INFO_DECODE_LOOKUP, + &[ + formatInfoBits1, + formatInfoBits2, + Self::MirrorBits(formatInfoBits1), + mirroredFormatInfoBits2, + ], + ); - // // Use bits 3/4 for error correction, and 0-2 for mask. - // fi.ecLevel = ECLevelFromBits((fi.index >> 3) & 0x03); - // fi.dataMask = static_cast(fi.index & 0x07); - // fi.isMirrored = fi.bitsIndex > 1; + // Use bits 3/4 for error correction, and 0-2 for mask. + fi.error_correction_level = + ErrorCorrectionLevel::ECLevelFromBits((fi.index >> 3) & 0x03, true); + fi.data_mask = fi.index & 0x07; + fi.isMirrored = fi.bitsIndex > 1; - // return fi; + fi } pub fn DecodeMQR(formatInfoBits: u32) -> Self { - todo!() - // // We don't use the additional masking (with 0x4445) to work around potentially non complying MicroQRCode encoders - // let fi = FindBestFormatInfo(0, FORMAT_INFO_DECODE_LOOKUP_MICRO, {formatInfoBits, MirrorBits(formatInfoBits)}); + // We don't use the additional masking (with 0x4445) to work around potentially non complying MicroQRCode encoders + let mut fi = Self::FindBestFormatInfo( + 0, + FORMAT_INFO_DECODE_LOOKUP_MICRO, + &[formatInfoBits, Self::MirrorBits(formatInfoBits)], + ); - // constexpr uint8_t BITS_TO_VERSION[] = {1, 2, 2, 3, 3, 4, 4, 4}; + const BITS_TO_VERSION: [u8; 8] = [1, 2, 2, 3, 3, 4, 4, 4]; - // // Bits 2/3/4 contain both error correction level and version, 0/1 contain mask. - // fi.ecLevel = ECLevelFromBits((fi.index >> 2) & 0x07, true); - // fi.dataMask = static_cast(fi.index & 0x03); - // fi.microVersion = BITS_TO_VERSION[(fi.index >> 2) & 0x07]; - // fi.isMirrored = fi.bitsIndex == 1; + // Bits 2/3/4 contain both error correction level and version, 0/1 contain mask. + fi.error_correction_level = + ErrorCorrectionLevel::ECLevelFromBits((fi.index >> 2) & 0x07, true); + fi.data_mask = (fi.index & 0x03); + fi.microVersion = BITS_TO_VERSION[((fi.index >> 2) & 0x07) as usize] as u32; + fi.isMirrored = fi.bitsIndex == 1; - // return fi; + fi } pub fn MirrorBits(bits: u32) -> u32 { @@ -42,7 +96,7 @@ impl FormatInformation { // return BitHacks::Reverse(bits) >> 17; } - pub fn FindBestFormatInfo(mask: u32, lookup: [(u32, u32); 32], bits: &[u32]) -> Self { + pub fn FindBestFormatInfo(mask: u32, lookup: [[u32; 2]; 32], bits: &[u32]) -> Self { todo!() // FormatInformation fi; diff --git a/src/qrcode/decoder/format_information.rs b/src/qrcode/decoder/format_information.rs index 65800c2..492b445 100644 --- a/src/qrcode/decoder/format_information.rs +++ b/src/qrcode/decoder/format_information.rs @@ -18,12 +18,12 @@ use crate::common::Result; use super::ErrorCorrectionLevel; -const FORMAT_INFO_MASK_QR: u32 = 0x5412; +pub const FORMAT_INFO_MASK_QR: u32 = 0x5412; /** * See ISO 18004:2006, Annex C, Table C.1 */ -const FORMAT_INFO_DECODE_LOOKUP: [[u32; 2]; 32] = [ +pub const FORMAT_INFO_DECODE_LOOKUP: [[u32; 2]; 32] = [ [0x5412, 0x00], [0x5125, 0x01], [0x5E7C, 0x02], @@ -73,6 +73,9 @@ pub struct FormatInformation { pub data_mask: u8, pub microVersion: u32, pub isMirrored: bool, + + pub index: u8, // = 255; + pub bitsIndex: u8, // = 255; } impl Default for FormatInformation { @@ -83,6 +86,8 @@ impl Default for FormatInformation { data_mask: Default::default(), microVersion: 0, isMirrored: false, + index: 255, + bitsIndex: 255, } } } @@ -99,6 +104,8 @@ impl FormatInformation { error_correction_level: errorCorrectionLevel, data_mask: dataMask, isMirrored: false, + index: 255, + bitsIndex: 255, }) } From 76fb937ca92fc38069561e3c447f7023dd983758 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 31 Mar 2023 11:58:29 -0500 Subject: [PATCH 34/72] removing todo!() --- .../cpp_essentials/base_extentions/qr_formatinformation.rs | 6 +++--- src/common/cpp_essentials/fast_edge_to_edge_counter.rs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs index ebd87e6..4198fc8 100644 --- a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs +++ b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs @@ -92,8 +92,7 @@ impl FormatInformation { } pub fn MirrorBits(bits: u32) -> u32 { - todo!() - // return BitHacks::Reverse(bits) >> 17; + (bits.reverse_bits()) >> 17 } pub fn FindBestFormatInfo(mask: u32, lookup: [[u32; 2]; 32], bits: &[u32]) -> Self { @@ -115,7 +114,8 @@ impl FormatInformation { // return fi; } + // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match pub fn isValid(&self) -> bool { - todo!() + self.hammingDistance <= 3 } } diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index c1a5fcb..1016c8a 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -4,6 +4,9 @@ pub struct FastEdgeToEdgeCounter { // const uint8_t* p = nullptr; // int stride = 0; // int stepsToBorder = 0; + p: usize, + stride: usize, // = 0; + stepsToBorder: usize, // = 0; } impl FastEdgeToEdgeCounter { From 612ef458f251d6e8976d2809e2898f5548df9c8c Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 31 Mar 2023 16:36:15 -0500 Subject: [PATCH 35/72] port FindBestFormatInfo --- .../base_extentions/qr_formatinformation.rs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs index 4198fc8..8eecd56 100644 --- a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs +++ b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs @@ -91,27 +91,34 @@ impl FormatInformation { fi } + #[inline(always)] pub fn MirrorBits(bits: u32) -> u32 { (bits.reverse_bits()) >> 17 } pub fn FindBestFormatInfo(mask: u32, lookup: [[u32; 2]; 32], bits: &[u32]) -> Self { - todo!() - // FormatInformation fi; + let mut fi = FormatInformation::default(); - // // Some QR codes apparently do not apply the XOR mask. Try without and with additional masking. - // for (auto mask : {0, mask}) - // for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex) - // for (const auto& [pattern, index] : lookup) { - // // Find the int in lookup with fewest bits differing - // if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); hammingDist < fi.hammingDistance) { - // fi.index = index; - // fi.hammingDistance = hammingDist; - // fi.bitsIndex = bitsIndex; - // } - // } + // Some QR codes apparently do not apply the XOR mask. Try without and with additional masking. + for mask in [0, mask] { + // for (auto mask : {0, mask}) + for bitsIndex in 0..bits.len() { + // for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex) + for [pattern, index] in lookup { + // for (const auto& [pattern, index] : lookup) { + // Find the int in lookup with fewest bits differing + let hammingDist = ((bits[bitsIndex] ^ mask) ^ pattern).count_ones(); + if hammingDist < fi.hammingDistance { + // if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); hammingDist < fi.hammingDistance) { + fi.index = index as u8; + fi.hammingDistance = hammingDist; + fi.bitsIndex = bitsIndex as u8; + } + } + } + } - // return fi; + fi } // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match From 097bfd1ead5322cf52dfd1b94607d62719d6ea68 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 31 Mar 2023 20:05:53 -0500 Subject: [PATCH 36/72] port FastEdgeToEdgeCounter::new --- src/common/bit_array.rs | 1 + .../cpp_essentials/bitmatrix_cursor_trait.rs | 4 +- src/common/cpp_essentials/edge_tracer.rs | 4 ++ .../fast_edge_to_edge_counter.rs | 45 ++++++++++++++----- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/common/bit_array.rs b/src/common/bit_array.rs index bb9e95b..26c1851 100644 --- a/src/common/bit_array.rs +++ b/src/common/bit_array.rs @@ -18,6 +18,7 @@ // import java.util.Arrays; +use std::ops::Index; use std::{cmp, fmt}; use crate::common::Result; diff --git a/src/common/cpp_essentials/bitmatrix_cursor_trait.rs b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs index 7ac1d17..4f4205e 100644 --- a/src/common/cpp_essentials/bitmatrix_cursor_trait.rs +++ b/src/common/cpp_essentials/bitmatrix_cursor_trait.rs @@ -1,4 +1,4 @@ -use crate::Point; +use crate::{common::BitMatrix, Point}; use super::{util::opposite, Direction, Value}; @@ -165,6 +165,8 @@ pub trait BitMatrixCursorTrait { fn d(&self) -> Point; + fn img(&self) -> &BitMatrix; + fn readPattern + Default + Copy + Clone>( &mut self, range: Option, diff --git a/src/common/cpp_essentials/edge_tracer.rs b/src/common/cpp_essentials/edge_tracer.rs index e22145d..88a8a3c 100644 --- a/src/common/cpp_essentials/edge_tracer.rs +++ b/src/common/cpp_essentials/edge_tracer.rs @@ -170,6 +170,10 @@ impl BitMatrixCursorTrait for EdgeTracer<'_> { fn d(&self) -> Point { self.d } + + fn img(&self) -> &BitMatrix { + self.img + } } impl<'a> EdgeTracer<'_> { diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index 1016c8a..3913f19 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -1,23 +1,48 @@ +use crate::common::BitArray; + use super::BitMatrixCursorTrait; pub struct FastEdgeToEdgeCounter { // const uint8_t* p = nullptr; // int stride = 0; // int stepsToBorder = 0; - p: usize, - stride: usize, // = 0; - stepsToBorder: usize, // = 0; + p: u32, // = nullptr; + stride: u32, // = 0; + stepsToBorder: u32, // = 0; + arr: BitArray, } impl FastEdgeToEdgeCounter { - pub fn new(_cur: &T) -> Self { - todo!() - // stride = cur.d.y * cur.img->width() + cur.d.x; - // p = cur.img->row(cur.p.y).begin() + cur.p.x; + pub fn new(cur: &T) -> Self { + let stride = cur.d().y as u32 * cur.img().width() as u32 + cur.d().x as u32; + let p = /*cur.img().getRow(cur.p().y).begin()*/ 0 + cur.p().x as u32; - // int maxStepsX = cur.d.x ? (cur.d.x > 0 ? cur.img->width() - 1 - cur.p.x : cur.p.x) : INT_MAX; - // int maxStepsY = cur.d.y ? (cur.d.y > 0 ? cur.img->height() - 1 - cur.p.y : cur.p.y) : INT_MAX; - // stepsToBorder = std::min(maxStepsX, maxStepsY); + let maxStepsX = if cur.d().x != 0.0 { + (if cur.d().x > 0.0 { + cur.img().width() - 1 - cur.p().x as u32 + } else { + cur.p().x as u32 + }) + } else { + u32::MAX + }; + let maxStepsY = if cur.d().y != 0.0 { + (if cur.d().y > 0.0 { + cur.img().height() - 1 - cur.p().y as u32 + } else { + cur.p().y as u32 + }) + } else { + u32::MAX + }; + let stepsToBorder = std::cmp::min(maxStepsX, maxStepsY); + + Self { + p, + stride, + stepsToBorder, + arr: cur.img().getRow(cur.p().y as u32), + } } pub fn stepToNextEdge(&self, _range: i32) -> i32 { From c103609550f9d0f53f9faacc48336b2df1c70244 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 31 Mar 2023 20:08:57 -0500 Subject: [PATCH 37/72] port stepToNextEdge --- .../fast_edge_to_edge_counter.rs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index 3913f19..10aad9f 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -45,22 +45,26 @@ impl FastEdgeToEdgeCounter { } } - pub fn stepToNextEdge(&self, _range: i32) -> i32 { - todo!() - // int maxSteps = std::min(stepsToBorder, range); - // int steps = 0; - // do { - // if (++steps > maxSteps) { - // if (maxSteps == stepsToBorder) - // break; - // else - // return 0; - // } - // } while (p[steps * stride] == p[0]); + pub fn stepToNextEdge(&self, range: u32) -> u32 { + let maxSteps = std::cmp::min(self.stepsToBorder, range); + let steps = 0; + loop { + steps += 1; + if (steps > maxSteps) { + if (maxSteps == self.stepsToBorder) { + break; + } else { + return 0; + } + } + if !(self.arr.get((steps * self.stride) as usize) == self.arr.get(0)) { + break; + } + } // while (p[steps * stride] == p[0]); - // p += steps * stride; - // stepsToBorder -= steps; + self.p += steps * self.stride; + self.stepsToBorder -= steps; - // return steps; + return steps; } } From 8dde4744f2a15c733eb045adb8d113409e7cd6f8 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 31 Mar 2023 20:27:58 -0500 Subject: [PATCH 38/72] actually use offset (p) --- src/common/cpp_essentials/fast_edge_to_edge_counter.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index 10aad9f..490cd84 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -45,7 +45,7 @@ impl FastEdgeToEdgeCounter { } } - pub fn stepToNextEdge(&self, range: u32) -> u32 { + pub fn stepToNextEdge(&mut self, range: u32) -> u32 { let maxSteps = std::cmp::min(self.stepsToBorder, range); let steps = 0; loop { @@ -57,7 +57,9 @@ impl FastEdgeToEdgeCounter { return 0; } } - if !(self.arr.get((steps * self.stride) as usize) == self.arr.get(0)) { + if !(self.arr.get((self.p + steps * self.stride) as usize) + == self.arr.get((self.p + 0) as usize)) + { break; } } // while (p[steps * stride] == p[0]); From 8e26bbe11750e702c789297f90eeb83a7c408a0c Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sat, 1 Apr 2023 09:43:41 -0500 Subject: [PATCH 39/72] fix errors due to change in sign --- src/common/cpp_essentials/concentric_finder.rs | 14 +++++++------- .../cpp_essentials/fast_edge_to_edge_counter.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index 9831bec..b207141 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -91,14 +91,14 @@ pub fn CheckSymmetricPattern< ) -> i32 { let mut range = range; - let curFwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(cur); - let curBwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(&cur.turnedBack()); + let mut curFwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(cur); + let mut curBwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(&cur.turnedBack()); - let centerFwd = curFwd.stepToNextEdge(range); + let centerFwd = curFwd.stepToNextEdge(range as u32) as i32; if centerFwd == 0 { return 0; } - let centerBwd = curBwd.stepToNextEdge(range); + let centerBwd = curBwd.stepToNextEdge(range as u32) as i32; if centerBwd == 0 { return 0; } @@ -109,8 +109,8 @@ pub fn CheckSymmetricPattern< res[s_2] = (centerFwd + centerBwd - 1) as u16; // -1 because the starting pixel is counted twice range -= res[s_2] as i32; - let mut next = |cur: &FastEdgeToEdgeCounter, i: isize| { - let v = cur.stepToNextEdge(range); + let mut next = |cur: &mut FastEdgeToEdgeCounter, i: isize| { + let v = cur.stepToNextEdge(range as u32) as i32; res[(s_2 as isize + i) as usize] = v as u16; range -= v; @@ -119,7 +119,7 @@ pub fn CheckSymmetricPattern< for i in 1..=s_2 { // for (int i = 1; i <= s_2; ++i) { - if next(&curFwd, i as isize) == 0 || next(&curBwd, -(i as isize)) == 0 { + if next(&mut curFwd, i as isize) == 0 || next(&mut curBwd, -(i as isize)) == 0 { return 0; } } diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index 490cd84..88ea88c 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -47,7 +47,7 @@ impl FastEdgeToEdgeCounter { pub fn stepToNextEdge(&mut self, range: u32) -> u32 { let maxSteps = std::cmp::min(self.stepsToBorder, range); - let steps = 0; + let mut steps = 0; loop { steps += 1; if (steps > maxSteps) { From 8fc6a240b353c6957a4e3b71e1dd2abb8bdca0b0 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sun, 2 Apr 2023 14:21:49 -0500 Subject: [PATCH 40/72] stubbing new reader / multireader --- src/qrcode/cpp_port/mod.rs | 3 + src/qrcode/cpp_port/qr_cpp_reader.rs | 272 +++++++++++++++++++++++++++ 2 files changed, 275 insertions(+) create mode 100644 src/qrcode/cpp_port/qr_cpp_reader.rs diff --git a/src/qrcode/cpp_port/mod.rs b/src/qrcode/cpp_port/mod.rs index 2893c1b..a604bc6 100644 --- a/src/qrcode/cpp_port/mod.rs +++ b/src/qrcode/cpp_port/mod.rs @@ -1,5 +1,8 @@ mod data_mask; pub mod decoder; pub mod detector; +mod qr_cpp_reader; + +pub use qr_cpp_reader::QrReader; mod bitmatrix_parser; diff --git a/src/qrcode/cpp_port/qr_cpp_reader.rs b/src/qrcode/cpp_port/qr_cpp_reader.rs new file mode 100644 index 0000000..f912693 --- /dev/null +++ b/src/qrcode/cpp_port/qr_cpp_reader.rs @@ -0,0 +1,272 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +// #include "QRReader.h" + +// #include "BinaryBitmap.h" +// #include "ConcentricFinder.h" +// #include "DecodeHints.h" +// #include "DecoderResult.h" +// #include "DetectorResult.h" +// #include "LogMatrix.h" +// #include "QRDecoder.h" +// #include "QRDetector.h" +// #include "Result.h" + +// #include + +// namespace ZXing::QRCode { + +// Result Reader::decode(const BinaryBitmap& image) const +// { +// #if 1 +// if (!_hints.isPure()) +// return FirstOrDefault(decode(image, 1)); +// #endif + +// auto binImg = image.getBitMatrix(); +// if (binImg == nullptr) +// return {}; + +// DetectorResult detectorResult; +// if (_hints.hasFormat(BarcodeFormat::QRCode)) +// detectorResult = DetectPureQR(*binImg); +// if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !detectorResult.isValid()) +// detectorResult = DetectPureMQR(*binImg); + +// if (!detectorResult.isValid()) +// return {}; + +// auto decoderResult = Decode(detectorResult.bits()); +// auto position = detectorResult.position(); + +// return Result(std::move(decoderResult), std::move(position), +// detectorResult.bits().width() < 21 ? BarcodeFormat::MicroQRCode : BarcodeFormat::QRCode); +// } + +// void logFPSet(const FinderPatternSet& fps [[maybe_unused]]) +// { +// #ifdef PRINT_DEBUG +// auto drawLine = [](PointF a, PointF b) { +// int steps = maxAbsComponent(b - a); +// PointF dir = bresenhamDirection(PointF(b - a)); +// for (int i = 0; i < steps; ++i) +// log(centered(a + i * dir), 2); +// }; + +// drawLine(fps.bl, fps.tl); +// drawLine(fps.tl, fps.tr); +// drawLine(fps.tr, fps.bl); +// #endif +// } + +// Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const +// { +// auto binImg = image.getBitMatrix(); +// if (binImg == nullptr) +// return {}; + +// #ifdef PRINT_DEBUG +// LogMatrixWriter lmw(log, *binImg, 5, "qr-log.pnm"); +// #endif + +// auto allFPs = FindFinderPatterns(*binImg, _hints.tryHarder()); + +// #ifdef PRINT_DEBUG +// printf("allFPs: %d\n", Size(allFPs)); +// #endif + +// std::vector usedFPs; +// Results results; + +// if (_hints.hasFormat(BarcodeFormat::QRCode)) { +// auto allFPSets = GenerateFinderPatternSets(allFPs); +// for (const auto& fpSet : allFPSets) { +// if (Contains(usedFPs, fpSet.bl) || Contains(usedFPs, fpSet.tl) || Contains(usedFPs, fpSet.tr)) +// continue; + +// logFPSet(fpSet); + +// auto detectorResult = SampleQR(*binImg, fpSet); +// if (detectorResult.isValid()) { +// auto decoderResult = Decode(detectorResult.bits()); +// auto position = detectorResult.position(); +// if (decoderResult.isValid()) { +// usedFPs.push_back(fpSet.bl); +// usedFPs.push_back(fpSet.tl); +// usedFPs.push_back(fpSet.tr); +// } +// if (decoderResult.isValid(_hints.returnErrors())) { +// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::QRCode); +// if (maxSymbols && Size(results) == maxSymbols) +// break; +// } +// } +// } +// } + +// if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { +// for (const auto& fp : allFPs) { +// if (Contains(usedFPs, fp)) +// continue; + +// auto detectorResult = SampleMQR(*binImg, fp); +// if (detectorResult.isValid()) { +// auto decoderResult = Decode(detectorResult.bits()); +// auto position = detectorResult.position(); +// if (decoderResult.isValid(_hints.returnErrors())) { +// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::MicroQRCode); +// if (maxSymbols && Size(results) == maxSymbols) +// break; +// } + +// } +// } +// } + +// return results; +// } + +// } // namespace ZXing::QRCode + + +use crate::{Reader, DecodingHintDictionary, multi::MultipleBarcodeReader}; + +pub struct QrReader{} + +impl Reader for QrReader { + fn decode(&mut self, image: &mut crate::BinaryBitmap) -> crate::common::Result { + self.decode_with_hints(image, &DecodingHintDictionary::new()) + } + + fn decode_with_hints( + &mut self, + image: &mut crate::BinaryBitmap, + hints: &crate::DecodingHintDictionary, + ) -> crate::common::Result { + todo!() + // #if 1 +// if (!_hints.isPure()) +// return FirstOrDefault(decode(image, 1)); +// #endif + +// auto binImg = image.getBitMatrix(); +// if (binImg == nullptr) +// return {}; + +// DetectorResult detectorResult; +// if (_hints.hasFormat(BarcodeFormat::QRCode)) +// detectorResult = DetectPureQR(*binImg); +// if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !detectorResult.isValid()) +// detectorResult = DetectPureMQR(*binImg); + +// if (!detectorResult.isValid()) +// return {}; + +// auto decoderResult = Decode(detectorResult.bits()); +// auto position = detectorResult.position(); + +// return Result(std::move(decoderResult), std::move(position), +// detectorResult.bits().width() < 21 ? BarcodeFormat::MicroQRCode : BarcodeFormat::QRCode); +// } + +// void logFPSet(const FinderPatternSet& fps [[maybe_unused]]) +// { +// #ifdef PRINT_DEBUG +// auto drawLine = [](PointF a, PointF b) { +// int steps = maxAbsComponent(b - a); +// PointF dir = bresenhamDirection(PointF(b - a)); +// for (int i = 0; i < steps; ++i) +// log(centered(a + i * dir), 2); +// }; + +// drawLine(fps.bl, fps.tl); +// drawLine(fps.tl, fps.tr); +// drawLine(fps.tr, fps.bl); +// #endif + } +} + +impl MultipleBarcodeReader for QrReader { + fn decode_multiple( + &mut self, + image: &mut crate::BinaryBitmap, + ) -> crate::common::Result> { + self.decode_multiple_with_hints(image, &DecodingHintDictionary::new()) + } + + fn decode_multiple_with_hints( + &mut self, + image: &mut crate::BinaryBitmap, + hints: &DecodingHintDictionary, + ) -> crate::common::Result> { + todo!() + // auto binImg = image.getBitMatrix(); +// if (binImg == nullptr) +// return {}; + +// #ifdef PRINT_DEBUG +// LogMatrixWriter lmw(log, *binImg, 5, "qr-log.pnm"); +// #endif + +// auto allFPs = FindFinderPatterns(*binImg, _hints.tryHarder()); + +// #ifdef PRINT_DEBUG +// printf("allFPs: %d\n", Size(allFPs)); +// #endif + +// std::vector usedFPs; +// Results results; + +// if (_hints.hasFormat(BarcodeFormat::QRCode)) { +// auto allFPSets = GenerateFinderPatternSets(allFPs); +// for (const auto& fpSet : allFPSets) { +// if (Contains(usedFPs, fpSet.bl) || Contains(usedFPs, fpSet.tl) || Contains(usedFPs, fpSet.tr)) +// continue; + +// logFPSet(fpSet); + +// auto detectorResult = SampleQR(*binImg, fpSet); +// if (detectorResult.isValid()) { +// auto decoderResult = Decode(detectorResult.bits()); +// auto position = detectorResult.position(); +// if (decoderResult.isValid()) { +// usedFPs.push_back(fpSet.bl); +// usedFPs.push_back(fpSet.tl); +// usedFPs.push_back(fpSet.tr); +// } +// if (decoderResult.isValid(_hints.returnErrors())) { +// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::QRCode); +// if (maxSymbols && Size(results) == maxSymbols) +// break; +// } +// } +// } +// } + +// if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { +// for (const auto& fp : allFPs) { +// if (Contains(usedFPs, fp)) +// continue; + +// auto detectorResult = SampleMQR(*binImg, fp); +// if (detectorResult.isValid()) { +// auto decoderResult = Decode(detectorResult.bits()); +// auto position = detectorResult.position(); +// if (decoderResult.isValid(_hints.returnErrors())) { +// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::MicroQRCode); +// if (maxSymbols && Size(results) == maxSymbols) +// break; +// } + +// } +// } +// } + +// return results; + } +} \ No newline at end of file From 1ef262e967357956dd6b0eb101435668920a8e37 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 3 Apr 2023 12:18:35 -0500 Subject: [PATCH 41/72] initial port complete, untested, builds --- src/barcode_format.rs | 6 + src/multi_format_reader.rs | 15 +- src/qrcode/cpp_port/detector.rs | 6 +- src/qrcode/cpp_port/qr_cpp_reader.rs | 312 +++++++++++++++++---------- src/qrcode/mod.rs | 2 +- 5 files changed, 219 insertions(+), 122 deletions(-) diff --git a/src/barcode_format.rs b/src/barcode_format.rs index 0f1f45f..43b0464 100644 --- a/src/barcode_format.rs +++ b/src/barcode_format.rs @@ -65,6 +65,8 @@ pub enum BarcodeFormat { /** QR Code 2D barcode format. */ QR_CODE, + MICRO_QR_CODE, + /** RSS 14 */ RSS_14, @@ -102,6 +104,7 @@ impl Display for BarcodeFormat { BarcodeFormat::MAXICODE => "maxicode", BarcodeFormat::PDF_417 => "pdf 417", BarcodeFormat::QR_CODE => "qrcode", + BarcodeFormat::MICRO_QR_CODE => "mqr", BarcodeFormat::RSS_14 => "rss 14", BarcodeFormat::RSS_EXPANDED => "rss expanded", BarcodeFormat::UPC_A => "upc a", @@ -140,6 +143,9 @@ impl From<&str> for BarcodeFormat { "maxicode" | "maxi_code" => BarcodeFormat::MAXICODE, "pdf 417" | "pdf_417" | "pdf417" | "iso 15438" | "iso_15438" => BarcodeFormat::PDF_417, "qrcode" | "qr_code" | "qr code" => BarcodeFormat::QR_CODE, + "mqr" | "microqr" | "micro_qr" | "micro_qrcode" | "micro_qr_code" | "mqr_code" => { + BarcodeFormat::MICRO_QR_CODE + } "rss 14" | "rss_14" | "rss14" | "gs1 databar" | "gs1 databar coupon" | "gs1_databar_coupon" => BarcodeFormat::RSS_14, "rss expanded" | "expanded rss" | "rss_expanded" => BarcodeFormat::RSS_EXPANDED, diff --git a/src/multi_format_reader.rs b/src/multi_format_reader.rs index 4d6fb5c..b6cb286 100644 --- a/src/multi_format_reader.rs +++ b/src/multi_format_reader.rs @@ -17,6 +17,7 @@ use std::collections::{HashMap, HashSet}; use crate::common::Result; +use crate::qrcode::cpp_port::QrReader; use crate::{ aztec::AztecReader, datamatrix::DataMatrixReader, maxicode::MaxiCodeReader, oned::MultiFormatOneDReader, pdf417::PDF417Reader, qrcode::QRCodeReader, BarcodeFormat, @@ -164,7 +165,16 @@ impl MultiFormatReader { for possible_format in self.possible_formats.iter() { let res = match possible_format { BarcodeFormat::QR_CODE => { - QRCodeReader::default().decode_with_hints(image, &self.hints) + let default_qr = + QRCodeReader::default().decode_with_hints(image, &self.hints); + if default_qr.is_ok() { + default_qr + } else { + QrReader::default().decode_with_hints(image, &self.hints) + } + } + BarcodeFormat::MICRO_QR_CODE => { + QrReader::default().decode_with_hints(image, &self.hints) } BarcodeFormat::DATA_MATRIX => { DataMatrixReader::default().decode_with_hints(image, &self.hints) @@ -199,6 +209,9 @@ impl MultiFormatReader { if let Ok(res) = QRCodeReader::default().decode_with_hints(image, &self.hints) { return Ok(res); } + if let Ok(res) = QrReader::default().decode_with_hints(image, &self.hints) { + return Ok(res); + } if let Ok(res) = DataMatrixReader::default().decode_with_hints(image, &self.hints) { return Ok(res); } diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index ac34c16..2afd886 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -27,9 +27,9 @@ use crate::{ #[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] pub struct FinderPatternSet { - bl: ConcentricPattern, - tl: ConcentricPattern, - tr: ConcentricPattern, + pub bl: ConcentricPattern, + pub tl: ConcentricPattern, + pub tr: ConcentricPattern, } pub type FinderPatterns = Vec; diff --git a/src/qrcode/cpp_port/qr_cpp_reader.rs b/src/qrcode/cpp_port/qr_cpp_reader.rs index f912693..1993fb9 100644 --- a/src/qrcode/cpp_port/qr_cpp_reader.rs +++ b/src/qrcode/cpp_port/qr_cpp_reader.rs @@ -5,22 +5,6 @@ */ // SPDX-License-Identifier: Apache-2.0 -// #include "QRReader.h" - -// #include "BinaryBitmap.h" -// #include "ConcentricFinder.h" -// #include "DecodeHints.h" -// #include "DecoderResult.h" -// #include "DetectorResult.h" -// #include "LogMatrix.h" -// #include "QRDecoder.h" -// #include "QRDetector.h" -// #include "Result.h" - -// #include - -// namespace ZXing::QRCode { - // Result Reader::decode(const BinaryBitmap& image) const // { // #if 1 @@ -133,13 +117,31 @@ // } // namespace ZXing::QRCode +use crate::{ + common::{cpp_essentials::ConcentricPattern, DetectorRXingResult, HybridBinarizer}, + multi::MultipleBarcodeReader, + BarcodeFormat, BinaryBitmap, DecodeHintType, DecodeHintValue, DecodingHintDictionary, + Exceptions, RXingResult, Reader, +}; -use crate::{Reader, DecodingHintDictionary, multi::MultipleBarcodeReader}; +use super::{ + decoder::Decode, + detector::{ + DetectPureMQR, DetectPureQR, FindFinderPatterns, GenerateFinderPatternSets, SampleMQR, + SampleQR, + }, +}; -pub struct QrReader{} +use crate::qrcode::detector::QRCodeDetectorResult as DetectorResult; + +#[derive(Default)] +pub struct QrReader; impl Reader for QrReader { - fn decode(&mut self, image: &mut crate::BinaryBitmap) -> crate::common::Result { + fn decode( + &mut self, + image: &mut crate::BinaryBitmap, + ) -> crate::common::Result { self.decode_with_hints(image, &DecodingHintDictionary::new()) } @@ -147,47 +149,67 @@ impl Reader for QrReader { &mut self, image: &mut crate::BinaryBitmap, hints: &crate::DecodingHintDictionary, - ) -> crate::common::Result { - todo!() + ) -> crate::common::Result { // #if 1 -// if (!_hints.isPure()) -// return FirstOrDefault(decode(image, 1)); -// #endif + if !matches!( + hints.get(&DecodeHintType::PURE_BARCODE), + Some(DecodeHintValue::PureBarcode(true)) + ) + // if !matches!(Some(hints.get(&DecodeHintType::PURE_BARCODE))) + // if (!_hints.isPure()) + { + return Ok(self + .decode_set_number_with_hints(image, hints, 1)? + .first() + .ok_or(Exceptions::NOT_FOUND)? + .clone()); + // return FirstOrDefault(decode(image, 1)); + } + // #endif -// auto binImg = image.getBitMatrix(); -// if (binImg == nullptr) -// return {}; + let binImg = image.get_black_matrix(); //image.getBitMatrix(); + // if (binImg == nullptr) + // {return {};} -// DetectorResult detectorResult; -// if (_hints.hasFormat(BarcodeFormat::QRCode)) -// detectorResult = DetectPureQR(*binImg); -// if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !detectorResult.isValid()) -// detectorResult = DetectPureMQR(*binImg); + let mut detectorResult = Err(Exceptions::NOT_FOUND); + if let Some(DecodeHintValue::PossibleFormats(formats)) = + hints.get(&DecodeHintType::POSSIBLE_FORMATS) + { + if formats.contains(&BarcodeFormat::QR_CODE) { + detectorResult = DetectPureQR(binImg); + } + if formats.contains(&BarcodeFormat::MICRO_QR_CODE) && detectorResult.is_err() { + detectorResult = DetectPureMQR(binImg); + } + } -// if (!detectorResult.isValid()) -// return {}; + let detectorResult = detectorResult?; -// auto decoderResult = Decode(detectorResult.bits()); -// auto position = detectorResult.position(); + // let detectorResult: DetectorResult; + // if (_hints.hasFormat(BarcodeFormat::QR_CODE)) + // {detectorResult = DetectPureQR(binImg);} -// return Result(std::move(decoderResult), std::move(position), -// detectorResult.bits().width() < 21 ? BarcodeFormat::MicroQRCode : BarcodeFormat::QRCode); -// } + // if (_hints.hasFormat(BarcodeFormat::MICRO_QR_CODE) && !detectorResult.isValid()) + // {detectorResult = DetectPureMQR(binImg);} -// void logFPSet(const FinderPatternSet& fps [[maybe_unused]]) -// { -// #ifdef PRINT_DEBUG -// auto drawLine = [](PointF a, PointF b) { -// int steps = maxAbsComponent(b - a); -// PointF dir = bresenhamDirection(PointF(b - a)); -// for (int i = 0; i < steps; ++i) -// log(centered(a + i * dir), 2); -// }; + // if (!detectorResult.isValid()) + // {return {};} -// drawLine(fps.bl, fps.tl); -// drawLine(fps.tl, fps.tr); -// drawLine(fps.tr, fps.bl); -// #endif + let decoderResult = Decode(detectorResult.getBits())?; + let position = detectorResult.getPoints(); + + 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); } } @@ -204,69 +226,125 @@ impl MultipleBarcodeReader for QrReader { image: &mut crate::BinaryBitmap, hints: &DecodingHintDictionary, ) -> crate::common::Result> { - todo!() - // auto binImg = image.getBitMatrix(); -// if (binImg == nullptr) -// return {}; - -// #ifdef PRINT_DEBUG -// LogMatrixWriter lmw(log, *binImg, 5, "qr-log.pnm"); -// #endif - -// auto allFPs = FindFinderPatterns(*binImg, _hints.tryHarder()); - -// #ifdef PRINT_DEBUG -// printf("allFPs: %d\n", Size(allFPs)); -// #endif - -// std::vector usedFPs; -// Results results; - -// if (_hints.hasFormat(BarcodeFormat::QRCode)) { -// auto allFPSets = GenerateFinderPatternSets(allFPs); -// for (const auto& fpSet : allFPSets) { -// if (Contains(usedFPs, fpSet.bl) || Contains(usedFPs, fpSet.tl) || Contains(usedFPs, fpSet.tr)) -// continue; - -// logFPSet(fpSet); - -// auto detectorResult = SampleQR(*binImg, fpSet); -// if (detectorResult.isValid()) { -// auto decoderResult = Decode(detectorResult.bits()); -// auto position = detectorResult.position(); -// if (decoderResult.isValid()) { -// usedFPs.push_back(fpSet.bl); -// usedFPs.push_back(fpSet.tl); -// usedFPs.push_back(fpSet.tr); -// } -// if (decoderResult.isValid(_hints.returnErrors())) { -// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::QRCode); -// if (maxSymbols && Size(results) == maxSymbols) -// break; -// } -// } -// } -// } - -// if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { -// for (const auto& fp : allFPs) { -// if (Contains(usedFPs, fp)) -// continue; - -// auto detectorResult = SampleMQR(*binImg, fp); -// if (detectorResult.isValid()) { -// auto decoderResult = Decode(detectorResult.bits()); -// auto position = detectorResult.position(); -// if (decoderResult.isValid(_hints.returnErrors())) { -// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::MicroQRCode); -// if (maxSymbols && Size(results) == maxSymbols) -// break; -// } - -// } -// } -// } - -// return results; + self.decode_set_number_with_hints(image, hints, u32::MAX) } -} \ No newline at end of file +} + +impl QrReader { + fn decode_set_number_with_hints( + &mut self, + image: &mut crate::BinaryBitmap, + hints: &DecodingHintDictionary, + count: u32, + ) -> crate::common::Result> { + let binImg = image.get_black_matrix(); //image.getBitMatrix(); + let maxSymbols = count; + // if (binImg == nullptr) + // {return {};} + + // #ifdef PRINT_DEBUG + // LogMatrixWriter lmw(log, *binImg, 5, "qr-log.pnm"); + // #endif + let try_harder = matches!( + hints.get(&DecodeHintType::TRY_HARDER), + Some(DecodeHintValue::TryHarder(true)) + ); + + let mut allFPs = FindFinderPatterns(binImg, try_harder); + + // #ifdef PRINT_DEBUG + // printf("allFPs: %d\n", Size(allFPs)); + // #endif + + let mut usedFPs: Vec = Vec::new(); + let mut results: Vec = 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) + }; + + if check_qr { + // if (_hints.hasFormat(BarcodeFormat::QRCode)) { + let allFPSets = GenerateFinderPatternSets(&mut allFPs); + for fpSet in allFPSets { + // for (const auto& fpSet : allFPSets) { + if (usedFPs.contains(&fpSet.bl) + || usedFPs.contains(&fpSet.tl) + || usedFPs.contains(&fpSet.tr)) + { + continue; + } + + // logFPSet(fpSet); + + let detectorResult = SampleQR(binImg, &fpSet); + 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()) { + usedFPs.push(fpSet.bl); + usedFPs.push(fpSet.tl); + usedFPs.push(fpSet.tr); + } + + if (decoderResult.isValid()) { + results.push(RXingResult::new( + &decoderResult.content().to_string(), + decoderResult.content().bytes().to_vec(), + position.to_vec(), + BarcodeFormat::QR_CODE, + )); + // results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::QR_CODE); + + if (maxSymbols != 0 && (results.len() as u32) == maxSymbols) { + break; + } + } + } + } + } + } + if (check_mqr && !(maxSymbols != 0 && (results.len() as u32) == maxSymbols)) { + // if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { + for fp in allFPs { + // for (const auto& fp : allFPs) { + if (usedFPs.contains(&fp)) { + continue; + } + + let detectorResult = SampleMQR(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::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; + } + } + } + } + } + } + + return Ok(results); + } +} diff --git a/src/qrcode/mod.rs b/src/qrcode/mod.rs index 77ee545..ae308d6 100644 --- a/src/qrcode/mod.rs +++ b/src/qrcode/mod.rs @@ -8,7 +8,7 @@ pub use qr_code_reader::*; mod qr_code_writer; pub use qr_code_writer::*; -mod cpp_port; +pub mod cpp_port; #[cfg(test)] #[cfg(feature = "image")] From 169db55a8201e72fb0642e4c6d853879b07cfc3c Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 3 Apr 2023 12:52:01 -0500 Subject: [PATCH 42/72] begin cleanup --- src/common/cpp_essentials/pattern.rs | 8 ++++++-- src/qrcode/cpp_port/detector.rs | 11 +++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index be4aef6..e356204 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -116,7 +116,11 @@ impl<'a> PatternView<'_> { Some(*self.data.0.get(self.start)?) } pub fn end(&self) -> Option { - Some(self.data.0[self.start + self.count]) + if self.start + self.count < self.data.0.len() { + Some(self.data.0[self.start + self.count]) + }else { + None + } } // int sum(int n = 0) const { return std::accumulate(_data, _data + (n == 0 ? _size : n), 0); } @@ -473,7 +477,7 @@ pub fn FindLeftGuardBy<'a, const LEN: usize, Pred: Fn(&PatternView, Option) if window.isAtFirstBar() && isGuard(&window, None) { return Ok(window); } - let end = Into::::into(view.end().unwrap()) - minSize; + let end = Into::::into(view.end().ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?) - minSize; while window.start < end { if isGuard(&window, Some(Into::::into(window[PREV_IDX]))) { return Ok(window); diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index 2afd886..cc0f090 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -61,8 +61,15 @@ pub fn FindFinderPatterns(image: &BitMatrix, tryHarder: bool) -> FinderPatterns let mut next: PatternView = PatternView::new(&row); while { - let next = FindLeftGuard(&next, 0, &PATTERN, 0.5).unwrap(); - next.isValid() + if let Ok(next) = FindLeftGuard(&next, 0, &PATTERN, 0.5) { + next.isValid() + }else { + false + } + // let Ok(next) = FindLeftGuard(&next, 0, &PATTERN, 0.5) else { + // break; + // }; + // next.isValid() } { let p = point( next.pixelsInFront() as f32 From 44e2d525993bd432375327966eb3aba037064184 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 3 Apr 2023 16:08:15 -0500 Subject: [PATCH 43/72] cleanup failures for existing tests --- src/common/cpp_essentials/pattern.rs | 4 ++-- src/qrcode/cpp_port/detector.rs | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index e356204..53366df 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -117,8 +117,8 @@ impl<'a> PatternView<'_> { } pub fn end(&self) -> Option { if self.start + self.count < self.data.0.len() { - Some(self.data.0[self.start + self.count]) - }else { + Some(self.data.0[self.start + self.count]) + } else { None } } diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index cc0f090..f0e159a 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -63,7 +63,7 @@ pub fn FindFinderPatterns(image: &BitMatrix, tryHarder: bool) -> FinderPatterns while { if let Ok(next) = FindLeftGuard(&next, 0, &PATTERN, 0.5) { next.isValid() - }else { + } else { false } // let Ok(next) = FindLeftGuard(&next, 0, &PATTERN, 0.5) else { @@ -134,6 +134,11 @@ pub fn GenerateFinderPatternSets(patterns: &mut FinderPatterns) -> FinderPattern let cosLower: f64 = (135.0_f64 / 180.0 * 3.1415).cos(); let nbPatterns = (patterns).len(); + + if nbPatterns < 2 { + return FinderPatternSets::default(); + } + for i in 0..(nbPatterns - 2) { // for (int i = 0; i < nbPatterns - 2; i++) { for j in (i + 1)..(nbPatterns - 1) { From c29a5547b41a51254b2673e221d224672e8bf6c5 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 3 Apr 2023 16:09:54 -0500 Subject: [PATCH 44/72] add specific cpp test cases --- tests/cpp_qr_code_blackbox_tests.rs | 146 ++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 tests/cpp_qr_code_blackbox_tests.rs diff --git a/tests/cpp_qr_code_blackbox_tests.rs b/tests/cpp_qr_code_blackbox_tests.rs new file mode 100644 index 0000000..856f235 --- /dev/null +++ b/tests/cpp_qr_code_blackbox_tests.rs @@ -0,0 +1,146 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use rxing::{ + qrcode::{cpp_port::QrReader, QRCodeReader}, + BarcodeFormat, MultiFormatReader, +}; + +mod common; + +/** + * @author Sean Owen + */ + +#[test] +fn qrcode_black_box1_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/qrcode-1", + QrReader::default(), + rxing::BarcodeFormat::QR_CODE, + ); + // super("src/test/resources/blackbox/qrcode-1", new MultiFormatReader(), BarcodeFormat.QR_CODE); + tester.add_test(17, 17, 0.0); + tester.add_test(14, 14, 90.0); + tester.add_test(17, 17, 180.0); + tester.add_test(14, 14, 270.0); + + tester.test_black_box(); +} + +/** + * @author Sean Owen + */ + +#[test] +fn qrcode_black_box2_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/qrcode-2", + // MultiFormatReader::default(), + QrReader {}, + BarcodeFormat::QR_CODE, + ); + tester.add_test(31, 31, 0.0); + tester.add_test(29, 29, 90.0); + tester.add_test(30, 30, 180.0); + tester.add_test(30, 30, 270.0); + + tester.test_black_box(); +} + +/** + * @author dswitkin@google.com (Daniel Switkin) + */ + +#[test] +fn qrcode_black_box3_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/qrcode-3", + QrReader::default(), + BarcodeFormat::QR_CODE, + ); + tester.add_test(38, 38, 0.0); + tester.add_test(39, 39, 90.0); + tester.add_test(36, 36, 180.0); + tester.add_test(39, 39, 270.0); + + tester.test_black_box(); +} + +/** + * Tests of various QR Codes from t-shirts, which are notoriously not flat. + * + * @author dswitkin@google.com (Daniel Switkin) + */ + +#[test] +fn qrcode_black_box4_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/qrcode-4", + QrReader::default(), + // QRCodeReader::new(), + BarcodeFormat::QR_CODE, + ); + tester.add_test(36, 36, 0.0); + tester.add_test(35, 35, 90.0); + tester.add_test(35, 35, 180.0); + tester.add_test(35, 35, 270.0); + + tester.test_black_box(); +} + +/** + * Some very difficult exposure conditions including self-shadowing, which happens a lot when + * pointing down at a barcode (i.e. the phone's shadow falls across part of the image). + * The global histogram gets about 5/15, where the local one gets 15/15. + * + * @author dswitkin@google.com (Daniel Switkin) + */ + +#[test] +fn qrcode_black_box5_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/qrcode-5", + QrReader::default(), + BarcodeFormat::QR_CODE, + ); + tester.add_test(19, 19, 0.0); + tester.add_test(19, 19, 90.0); + tester.add_test(19, 19, 180.0); + tester.add_test(19, 19, 270.0); + + tester.test_black_box(); +} + +/** + * These tests are supplied by Tim Gernat and test finder pattern detection at small size and under + * rotation, which was a weak spot. + */ + +#[test] +fn qrcode_black_box6_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/qrcode-6", + QrReader::default(), + BarcodeFormat::QR_CODE, + ); + tester.add_test(15, 15, 0.0); + tester.add_test(14, 14, 90.0); + tester.add_test(13, 13, 180.0); + tester.add_test(14, 14, 270.0); + + tester.test_black_box(); +} From 8a1d0b3969f14880754f2dd0e49e0dbff3a30f92 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Wed, 5 Apr 2023 12:26:34 -0500 Subject: [PATCH 45/72] Beginning port of tests for qrcode from c++ --- .../base_extentions/qr_formatinformation.rs | 2 +- src/qrcode/cpp_port/mod.rs | 3 + src/qrcode/cpp_port/test/MQRDecoderTest.cpp | 124 ++++ .../cpp_port/test/QRBitMatrixParserTest.cpp | 73 ++ src/qrcode/cpp_port/test/QRDataMaskTest.cpp | 100 +++ .../test/QRDecodedBitStreamParserTest.cpp | 134 ++++ src/qrcode/cpp_port/test/QREncoderTest.cpp | 657 ++++++++++++++++++ .../test/QRErrorCorrectionLevelTest.cpp | 45 ++ .../cpp_port/test/QRFormatInformationTest.rs | 124 ++++ src/qrcode/cpp_port/test/QRModeTest.rs | 135 ++++ src/qrcode/cpp_port/test/QRVersionTest.rs | 122 ++++ src/qrcode/cpp_port/test/QRWriterTest.cpp | 160 +++++ src/qrcode/cpp_port/test/mod.rs | 4 + src/qrcode/decoder/version.rs | 56 +- 14 files changed, 1715 insertions(+), 24 deletions(-) create mode 100644 src/qrcode/cpp_port/test/MQRDecoderTest.cpp create mode 100644 src/qrcode/cpp_port/test/QRBitMatrixParserTest.cpp create mode 100644 src/qrcode/cpp_port/test/QRDataMaskTest.cpp create mode 100644 src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.cpp create mode 100644 src/qrcode/cpp_port/test/QREncoderTest.cpp create mode 100644 src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.cpp create mode 100644 src/qrcode/cpp_port/test/QRFormatInformationTest.rs create mode 100644 src/qrcode/cpp_port/test/QRModeTest.rs create mode 100644 src/qrcode/cpp_port/test/QRVersionTest.rs create mode 100644 src/qrcode/cpp_port/test/QRWriterTest.cpp create mode 100644 src/qrcode/cpp_port/test/mod.rs diff --git a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs index 8eecd56..90b89de 100644 --- a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs +++ b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs @@ -64,7 +64,7 @@ impl FormatInformation { // Use bits 3/4 for error correction, and 0-2 for mask. fi.error_correction_level = - ErrorCorrectionLevel::ECLevelFromBits((fi.index >> 3) & 0x03, true); + ErrorCorrectionLevel::ECLevelFromBits((fi.index >> 3) & 0x03, false); fi.data_mask = fi.index & 0x07; fi.isMirrored = fi.bitsIndex > 1; diff --git a/src/qrcode/cpp_port/mod.rs b/src/qrcode/cpp_port/mod.rs index a604bc6..f18f038 100644 --- a/src/qrcode/cpp_port/mod.rs +++ b/src/qrcode/cpp_port/mod.rs @@ -6,3 +6,6 @@ mod qr_cpp_reader; pub use qr_cpp_reader::QrReader; mod bitmatrix_parser; + +#[cfg(test)] +mod test; diff --git a/src/qrcode/cpp_port/test/MQRDecoderTest.cpp b/src/qrcode/cpp_port/test/MQRDecoderTest.cpp new file mode 100644 index 0000000..25e7f70 --- /dev/null +++ b/src/qrcode/cpp_port/test/MQRDecoderTest.cpp @@ -0,0 +1,124 @@ +/* + * Copyright 2017 Huy Cuong Nguyen + * Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "qrcode/QRDecoder.h" + +#include "BitMatrix.h" +#include "BitMatrixIO.h" +#include "DecoderResult.h" + +#include "gtest/gtest.h" + +using namespace ZXing; +using namespace ZXing::QRCode; + +TEST(MQRDecoderTest, MQRCodeM3L) +{ + const auto bitMatrix = ParseBitMatrix("XXXXXXX X X X X\n" + "X X X X \n" + "X XXX X XXXXXXX\n" + "X XXX X X X XX\n" + "X XXX X X XX\n" + "X X X X X X\n" + "XXXXXXX X XX \n" + " X X X\n" + "XXXXXX X X X\n" + " X XX XXX\n" + "XXX XX XXXX XXX\n" + " X X XXX X \n" + "X XXXXX XXX X X\n" + " X X X XXX \n" + "XXX XX X X XXXX\n", + 88, false); + + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); +} + +TEST(MQRDecoderTest, MQRCodeM3M) +{ + const auto bitMatrix = ParseBitMatrix("XXXXXXX X X X X\n" + "X X XX\n" + "X XXX X X XX XX\n" + "X XXX X X X \n" + "X XXX X XX XXXX\n" + "X X XX \n" + "XXXXXXX X XXXX\n" + " X XXX \n" + "X XX XX X X\n" + " X X XX \n" + "XX XX XXXXXXX\n" + " X X X\n" + "XX X X X \n" + " X X X \n" + "X X XXXX XXX\n", + 88, false); + + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); +} + +TEST(MQRDecoderTest, MQRCodeM1) +{ + const auto bitMatrix = ParseBitMatrix("XXXXXXX X X\n" + "X X \n" + "X XXX X XXX\n" + "X XXX X XX\n" + "X XXX X X\n" + "X X XX \n" + "XXXXXXX X \n" + " X \n" + "XX X \n" + " X XXXXX X\n" + "X XXXXXX X\n", + 88, false); + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); + EXPECT_EQ(L"123", result.text()); +} + +TEST(MQRDecoderTest, MQRCodeM1Error4Bits) +{ + const auto bitMatrix = ParseBitMatrix("XXXXXXX X X\n" + "X X XX\n" + "X XXX X X \n" + "X XXX X XX\n" + "X XXX X X\n" + "X X XX \n" + "XXXXXXX X \n" + " X \n" + "XX X \n" + " X XXXXXX \n" + "X XXXXXXX \n", + 88, false); + const auto result = Decode(bitMatrix); + EXPECT_EQ(Error::Checksum, result.error()); + EXPECT_TRUE(result.text().empty()); +} + +TEST(MQRDecoderTest, MQRCodeM4) +{ + const auto bitMatrix = ParseBitMatrix("XXXXXXX X X X X X\n" + "X X XX X XX\n" + "X XXX X X X XX\n" + "X XXX X XX XX XX\n" + "X XXX X X XXXXX\n" + "X X XX X\n" + "XXXXXXX XX X XX\n" + " X XX XX\n" + "X X XXX X XXX\n" + " XX X XX XX X \n" + "XX XXXX X XX XX\n" + " XX XX X XX XX\n" + "XXX XXX XXX XX XX\n" + " X X X XX X\n" + "X X XX XXXXX \n" + " X X X X X \n" + "X XXXXXXX X X X\n", + 88, false); + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); +} diff --git a/src/qrcode/cpp_port/test/QRBitMatrixParserTest.cpp b/src/qrcode/cpp_port/test/QRBitMatrixParserTest.cpp new file mode 100644 index 0000000..f8c5a93 --- /dev/null +++ b/src/qrcode/cpp_port/test/QRBitMatrixParserTest.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2017 Huy Cuong Nguyen + * Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "BitMatrix.h" +#include "BitMatrixIO.h" +#include "ByteArray.h" +#include "qrcode/QRBitMatrixParser.h" +#include "qrcode/QRFormatInformation.h" +#include "qrcode/QRVersion.h" + +#include "gtest/gtest.h" + +using namespace ZXing; +using namespace ZXing::QRCode; + +TEST(QRBitMatrixParserTest, MQRCodeM3L) +{ + const auto bitMatrix = ParseBitMatrix("XXXXXXX X X X X\n" + "X X X X \n" + "X XXX X XXXXXXX\n" + "X XXX X X X XX\n" + "X XXX X X XX\n" + "X X X X X X\n" + "XXXXXXX X XX \n" + " X X X\n" + "XXXXXX X X X\n" + " X XX XXX\n" + "XXX XX XXXX XXX\n" + " X X XXX X \n" + "X XXXXX XXX X X\n" + " X X X XXX \n" + "XXX XX X X XXXX\n", + 88, false); + + const auto version = ReadVersion(bitMatrix); + EXPECT_EQ(3, version->versionNumber()); + const auto format = ReadFormatInformation(bitMatrix, true); + const auto codewords = ReadCodewords(bitMatrix, *version, format); + EXPECT_EQ(17, codewords.size()); + EXPECT_EQ(0x0, codewords[10]); + EXPECT_EQ(0xd1, codewords[11]); +} + +TEST(QRBitMatrixParserTest, MQRCodeM3M) +{ + const auto bitMatrix = ParseBitMatrix("XXXXXXX X X X X\n" + "X X XX\n" + "X XXX X X XX XX\n" + "X XXX X X X \n" + "X XXX X XX XXXX\n" + "X X XX \n" + "XXXXXXX X XXXX\n" + " X XXX \n" + "X XX XX X X\n" + " X X XX \n" + "XX XX XXXXXXX\n" + " X X X\n" + "XX X X X \n" + " X X X \n" + "X X XXXX XXX\n", + 88, false); + + const auto version = ReadVersion(bitMatrix); + EXPECT_EQ(3, version->versionNumber()); + const auto format = ReadFormatInformation(bitMatrix, true); + const auto codewords = ReadCodewords(bitMatrix, *version, format); + EXPECT_EQ(17, codewords.size()); + EXPECT_EQ(0x0, codewords[8]); + EXPECT_EQ(0x89, codewords[9]); +} diff --git a/src/qrcode/cpp_port/test/QRDataMaskTest.cpp b/src/qrcode/cpp_port/test/QRDataMaskTest.cpp new file mode 100644 index 0000000..24baa4a --- /dev/null +++ b/src/qrcode/cpp_port/test/QRDataMaskTest.cpp @@ -0,0 +1,100 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2007 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "qrcode/QRDataMask.h" +#include "BitMatrix.h" + +#include "gtest/gtest.h" +#include + +using namespace ZXing; +using namespace ZXing::QRCode; + +namespace { + + void TestMaskAcrossDimensionsImpl(int maskIndex, bool isMicro, const int versionMax, const int dimensionStart, const int dimensionStep, std::function condition) + { + for (int version = 1; version <= versionMax; version++) { + int dimension = dimensionStart + dimensionStep * version; + BitMatrix bits(dimension); + + for (int i = 0; i < dimension; i++) + for (int j = 0; j < dimension; j++) + EXPECT_EQ(GetMaskedBit(bits, j, i, maskIndex, isMicro), condition(i, j)) << "(" << i << ',' << j << ')'; + } + } + + void TestMaskAcrossDimensions(int maskIndex, std::function condition) + { + TestMaskAcrossDimensionsImpl(maskIndex, false, 40, 17, 4, condition); + } + + void TestMicroMaskAcrossDimensions(int maskIndex, std::function condition) + { + TestMaskAcrossDimensionsImpl(maskIndex, true, 4, 9, 2, condition); + } + +} + +TEST(QRDataMaskTest, Mask0) +{ + TestMaskAcrossDimensions(0, [](int i, int j) { return (i + j) % 2 == 0; }); +} + +TEST(QRDataMaskTest, Mask1) +{ + TestMaskAcrossDimensions(1, [](int i, int) { return i % 2 == 0; }); +} + +TEST(QRDataMaskTest, Mask2) +{ + TestMaskAcrossDimensions(2, [](int, int j) { return j % 3 == 0; }); +} + +TEST(QRDataMaskTest, Mask3) +{ + TestMaskAcrossDimensions(3, [](int i, int j) { return (i + j) % 3 == 0; }); +} + +TEST(QRDataMaskTest, Mask4) +{ + TestMaskAcrossDimensions(4, [](int i, int j) { return (i / 2 + j / 3) % 2 == 0; }); +} + +TEST(QRDataMaskTest, Mask5) +{ + TestMaskAcrossDimensions(5, [](int i, int j) { return (i * j) % 2 + (i * j) % 3 == 0; }); +} + +TEST(QRDataMaskTest, Mask6) +{ + TestMaskAcrossDimensions(6, [](int i, int j) { return ((i * j) % 2 + (i * j) % 3) % 2 == 0; }); +} + +TEST(QRDataMaskTest, Mask7) +{ + TestMaskAcrossDimensions(7, [](int i, int j) { return ((i + j) % 2 + (i * j) % 3) % 2 == 0; }); +} + +TEST(QRDataMaskTest, MicroMask0) +{ + TestMicroMaskAcrossDimensions(0, [](int i, int) { return i % 2 == 0; }); +} + +TEST(QRDataMaskTest, MicroMask1) +{ + TestMicroMaskAcrossDimensions(1, [](int i, int j) { return (i / 2 + j / 3) % 2 == 0; }); +} + +TEST(QRDataMaskTest, MicroMask2) +{ + TestMicroMaskAcrossDimensions(2, [](int i, int j) { return ((i * j) % 2 + (i * j) % 3) % 2 == 0; }); +} + +TEST(QRDataMaskTest, MicroMask3) +{ + TestMicroMaskAcrossDimensions(3, [](int i, int j) { return ((i + j) % 2 + (i * j) % 3) % 2 == 0; }); +} diff --git a/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.cpp b/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.cpp new file mode 100644 index 0000000..66d4f89 --- /dev/null +++ b/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.cpp @@ -0,0 +1,134 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "BitArray.h" +#include "ByteArray.h" +#include "DecoderResult.h" +#include "qrcode/QRDataMask.h" +#include "qrcode/QRDecoder.h" +#include "qrcode/QRErrorCorrectionLevel.h" +#include "qrcode/QRVersion.h" + +#include "gtest/gtest.h" + +namespace ZXing { + namespace QRCode { + DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel ecLevel); + } +} + +using namespace ZXing; +using namespace ZXing::QRCode; + +TEST(QRDecodedBitStreamParserTest, SimpleByteMode) +{ + BitArray ba; + ba.appendBits(0x04, 4); // Byte mode + ba.appendBits(0x03, 8); // 3 bytes + ba.appendBits(0xF1, 8); + ba.appendBits(0xF2, 8); + ba.appendBits(0xF3, 8); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); + EXPECT_EQ(L"\xF1\xF2\xF3", result); +} + +TEST(QRDecodedBitStreamParserTest, SimpleSJIS) +{ + BitArray ba; + ba.appendBits(0x04, 4); // Byte mode + ba.appendBits(0x04, 8); // 4 bytes + ba.appendBits(0xA1, 8); + ba.appendBits(0xA2, 8); + ba.appendBits(0xA3, 8); + ba.appendBits(0xD0, 8); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); + EXPECT_EQ(L"\uff61\uff62\uff63\uff90", result); +} + +TEST(QRDecodedBitStreamParserTest, ECI) +{ + BitArray ba; + ba.appendBits(0x07, 4); // ECI mode + ba.appendBits(0x02, 8); // ECI 2 = CP437 encoding + ba.appendBits(0x04, 4); // Byte mode + ba.appendBits(0x03, 8); // 3 bytes + ba.appendBits(0xA1, 8); + ba.appendBits(0xA2, 8); + ba.appendBits(0xA3, 8); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); + EXPECT_EQ(L"\xED\xF3\xFA", result); +} + +TEST(QRDecodedBitStreamParserTest, Hanzi) +{ + BitArray ba; + ba.appendBits(0x0D, 4); // Hanzi mode + ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding + ba.appendBits(0x01, 8); // 1 characters + ba.appendBits(0x03C1, 13); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); + EXPECT_EQ(L"\u963f", result); +} + +TEST(QRDecodedBitStreamParserTest, HanziLevel1) +{ + BitArray ba; + ba.appendBits(0x0D, 4); // Hanzi mode + ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding + ba.appendBits(0x01, 8); // 1 characters + // A5A2 (U+30A2) => A5A2 - A1A1 = 401, 4*60 + 01 = 0181 + ba.appendBits(0x0181, 13); + + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); + EXPECT_EQ(L"\u30a2", result); +} + +TEST(QRDecodedBitStreamParserTest, SymbologyIdentifier) +{ + const Version& version = *Version::FromNumber(1); + const ErrorCorrectionLevel ecLevel = ErrorCorrectionLevel::Medium; + DecoderResult result; + + // Plain "ANUM(1) A" + result = DecodeBitStream({0x20, 0x09, 0x40}, version, ecLevel); + EXPECT_EQ(result.symbologyIdentifier(), "]Q1"); + EXPECT_EQ(result.text(), L"A"); + + // GS1 "FNC1(1st) NUM(4) 2001" + result = DecodeBitStream({0x51, 0x01, 0x0C, 0x81, 0x00}, version, ecLevel); + EXPECT_EQ(result.symbologyIdentifier(), "]Q3"); + EXPECT_EQ(result.text(), L"2001"); // "(20)01" + + // GS1 "NUM(4) 2001 FNC1(1st) 301" - FNC1(1st) can occur anywhere (this actually violates the specification) + result = DecodeBitStream({0x10, 0x10, 0xC8, 0x15, 0x10, 0x0D, 0x2D, 0x00}, version, ecLevel); + EXPECT_EQ(result.symbologyIdentifier(), "]Q3"); + EXPECT_EQ(result.text(), L"2001301"); // "(20)01(30)1" + + // AIM "FNC1(2nd) 99 (0x63) ANUM(1) A" + result = DecodeBitStream({0x96, 0x32, 0x00, 0x94, 0x00}, version, ecLevel); + EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); + EXPECT_EQ(result.text(), L"99A"); + + // AIM "BYTE(1) A FNC1(2nd) 99 (0x63) BYTE(1) B" - FNC1(2nd) can occur anywhere + // Disabled this test, since this violates the specification and the code does support it anymore +// result = DecodeBitStream({0x40, 0x14, 0x19, 0x63, 0x40, 0x14, 0x20, 0x00}, version, ecLevel, ""); +// EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); +// EXPECT_EQ(result.text(), L"99AB"); // Application Indicator prefixed to data + + // AIM "FNC1(2nd) A (100 + 61 = 0xA5) ANUM(1) B" + result = DecodeBitStream({0x9A, 0x52, 0x00, 0x96, 0x00}, version, ecLevel); + EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); + EXPECT_EQ(result.text(), L"AB"); + + // AIM "FNC1(2nd) a (100 + 97 = 0xC5) ANUM(1) B" + result = DecodeBitStream({0x9C, 0x52, 0x00, 0x96, 0x00}, version, ecLevel); + EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); + EXPECT_EQ(result.text(), L"aB"); + + // Bad AIM Application Indicator "FNC1(2nd) @ (0xA4) ANUM(1) B" + result = DecodeBitStream({0x9A, 0x42, 0x00, 0x96, 0x00}, version, ecLevel); + EXPECT_FALSE(result.isValid()); +} diff --git a/src/qrcode/cpp_port/test/QREncoderTest.cpp b/src/qrcode/cpp_port/test/QREncoderTest.cpp new file mode 100644 index 0000000..8e28abc --- /dev/null +++ b/src/qrcode/cpp_port/test/QREncoderTest.cpp @@ -0,0 +1,657 @@ +/* + * Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "BitArray.h" +#include "BitArrayUtility.h" +#include "BitMatrixIO.h" +#include "CharacterSet.h" +#include "TextDecoder.h" +#include "Utf.h" +#include "qrcode/QREncoder.h" +#include "qrcode/QRCodecMode.h" +#include "qrcode/QREncodeResult.h" +#include "qrcode/QRErrorCorrectionLevel.h" + +#include "gtest/gtest.h" + +namespace ZXing { + namespace QRCode { + int GetAlphanumericCode(int code); + CodecMode ChooseMode(const std::wstring& content, CharacterSet encoding); + void AppendModeInfo(CodecMode mode, BitArray& bits); + void AppendLengthInfo(int numLetters, const Version& version, CodecMode mode, BitArray& bits); + void AppendNumericBytes(const std::wstring& content, BitArray& bits); + void AppendAlphanumericBytes(const std::wstring& content, BitArray& bits); + void Append8BitBytes(const std::wstring& content, CharacterSet encoding, BitArray& bits); + void AppendKanjiBytes(const std::wstring& content, BitArray& bits); + void AppendBytes(const std::wstring& content, CodecMode mode, CharacterSet encoding, BitArray& bits); + void TerminateBits(int numDataBytes, BitArray& bits); + void GetNumDataBytesAndNumECBytesForBlockID(int numTotalBytes, int numDataBytes, int numRSBlocks, int blockID, int& numDataBytesInBlock, int& numECBytesInBlock); + void GenerateECBytes(const ByteArray& dataBytes, int numEcBytesInBlock, ByteArray& ecBytes); + BitArray InterleaveWithECBytes(const BitArray& bits, int numTotalBytes, int numDataBytes, int numRSBlocks); + } +} + +using namespace ZXing; +using namespace ZXing::QRCode; +using namespace ZXing::Utility; + +namespace { + std::wstring ShiftJISString(const std::vector& bytes) + { + std::string str; + TextDecoder::Append(str, bytes.data(), bytes.size(), CharacterSet::Shift_JIS); + return FromUtf8(str); + } + + std::string RemoveSpace(std::string s) + { + s.erase(std::remove(s.begin(), s.end(), ' '), s.end()); + return s; + } +} + +TEST(QREncoderTest, GetAlphanumericCode) +{ + // The first ten code points are numbers. + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(i, GetAlphanumericCode('0' + i)); + } + + // The next 26 code points are capital alphabet letters. + for (int i = 10; i < 36; ++i) { + EXPECT_EQ(i, GetAlphanumericCode('A' + i - 10)); + } + + // Others are symbol letters + EXPECT_EQ(36, GetAlphanumericCode(' ')); + EXPECT_EQ(37, GetAlphanumericCode('$')); + EXPECT_EQ(38, GetAlphanumericCode('%')); + EXPECT_EQ(39, GetAlphanumericCode('*')); + EXPECT_EQ(40, GetAlphanumericCode('+')); + EXPECT_EQ(41, GetAlphanumericCode('-')); + EXPECT_EQ(42, GetAlphanumericCode('.')); + EXPECT_EQ(43, GetAlphanumericCode('/')); + EXPECT_EQ(44, GetAlphanumericCode(':')); + + // Should return -1 for other letters; + EXPECT_EQ(-1, GetAlphanumericCode('a')); + EXPECT_EQ(-1, GetAlphanumericCode('#')); + EXPECT_EQ(-1, GetAlphanumericCode('\0')); +} + +TEST(QREncoderTest, ChooseMode) +{ + // Numeric mode. + EXPECT_EQ(CodecMode::NUMERIC, ChooseMode(L"0", CharacterSet::Unknown)); + EXPECT_EQ(CodecMode::NUMERIC, ChooseMode(L"0123456789", CharacterSet::Unknown)); + // Alphanumeric mode. + EXPECT_EQ(CodecMode::ALPHANUMERIC, ChooseMode(L"A", CharacterSet::Unknown)); + EXPECT_EQ(CodecMode::ALPHANUMERIC, + ChooseMode(L"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:", CharacterSet::Unknown)); + // 8-bit byte mode. + EXPECT_EQ(CodecMode::BYTE, ChooseMode(L"a", CharacterSet::Unknown)); + EXPECT_EQ(CodecMode::BYTE, ChooseMode(L"#", CharacterSet::Unknown)); + EXPECT_EQ(CodecMode::BYTE, ChooseMode(L"", CharacterSet::Unknown)); + // Kanji mode. We used to use MODE_KANJI for these, but we stopped + // doing that as we cannot distinguish Shift_JIS from other encodings + // from data bytes alone. See also comments in qrcode_encoder.h. + + // AIUE in Hiragana in Shift_JIS + EXPECT_EQ(CodecMode::BYTE, + ChooseMode(ShiftJISString({0x8, 0xa, 0x8, 0xa, 0x8, 0xa, 0x8, 0xa6}), CharacterSet::Unknown)); + + // Nihon in Kanji in Shift_JIS. + EXPECT_EQ(CodecMode::BYTE, ChooseMode(ShiftJISString({0x9, 0xf, 0x9, 0x7b}), CharacterSet::Unknown)); + + // Sou-Utsu-Byou in Kanji in Shift_JIS. + EXPECT_EQ(CodecMode::BYTE, ChooseMode(ShiftJISString({0xe, 0x4, 0x9, 0x5, 0x9, 0x61}), CharacterSet::Unknown)); +} + +TEST(QREncoderTest, Encode) +{ + auto qrCode = Encode(L"ABCDEF", ErrorCorrectionLevel::High, CharacterSet::Unknown, 0, false, -1); + EXPECT_EQ(qrCode.mode, CodecMode::ALPHANUMERIC); + EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::High); + ASSERT_NE(qrCode.version, nullptr); + EXPECT_EQ(qrCode.version->versionNumber(), 1); + EXPECT_EQ(qrCode.maskPattern, 4); + EXPECT_EQ(ToString(qrCode.matrix), + "X X X X X X X X X X X X X X X X \n" + "X X X X X X X \n" + "X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X X X \n" + "X X X X X X X X X X X X X X X X X \n" + " X X \n" + " X X X X X X X X X X \n" + " X X X X X X X X X X X X \n" + "X X X X X X X X X X \n" + "X X X X X X X X X \n" + " X X X X X X X X X X X X X \n" + " X X X X X X \n" + "X X X X X X X X X X X X X \n" + "X X X X X X X X X X \n" + "X X X X X X X X X X X \n" + "X X X X X X X X X X X \n" + "X X X X X X X X X \n" + "X X X X X X X \n" + "X X X X X X X X X X X X \n"); +} + +TEST(QREncoderTest, EncodeWithVersion) +{ + auto qrCode = Encode(L"ABCDEF", ErrorCorrectionLevel::High, CharacterSet::Unknown, 7, false, -1); + ASSERT_NE(qrCode.version, nullptr); + EXPECT_EQ(qrCode.version->versionNumber(), 7); +} + +TEST(QREncoderTest, EncodeWithVersionTooSmall) +{ + EXPECT_THROW( + Encode(L"THISMESSAGEISTOOLONGFORAQRCODEVERSION3", ErrorCorrectionLevel::High, CharacterSet::Unknown, 3, false, -1) + , std::invalid_argument); +} + +TEST(QREncoderTest, SimpleUTF8ECI) +{ + auto qrCode = Encode(L"hello", ErrorCorrectionLevel::High, CharacterSet::UTF8, 0, false, -1); + EXPECT_EQ(qrCode.mode, CodecMode::BYTE); + EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::High); + ASSERT_NE(qrCode.version, nullptr); + EXPECT_EQ(qrCode.version->versionNumber(), 1); + EXPECT_EQ(qrCode.maskPattern, 6); + EXPECT_EQ(ToString(qrCode.matrix), // break the line comment + "X X X X X X X X X X X X X X X X \n" + "X X X X X X \n" + "X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X \n" + "X X X X X X X X X X X X X X X X X \n" + " X X X X \n" + " X X X X X X X \n" + " X X X X X X X X X \n" + "X X X X X X X X X X X \n" + " X X X X X X \n" + " X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X \n" + "X X X X X X X X X X \n" + "X X X X X X \n" + "X X X X X X X X X \n" + "X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X X \n" + "X X X X X X X X X X X \n"); +} + +TEST(QREncoderTest, SimpleBINARYECI) +{ + auto qrCode = Encode(L"\u00E9", ErrorCorrectionLevel::High, CharacterSet::BINARY, 0, false, -1); + EXPECT_EQ(qrCode.mode, CodecMode::BYTE); + EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::High); + ASSERT_NE(qrCode.version, nullptr); + EXPECT_EQ(qrCode.version->versionNumber(), 1); + EXPECT_EQ(qrCode.maskPattern, 6); + EXPECT_EQ(ToString(qrCode.matrix), + "X X X X X X X X X X X X X X X X X X \n" + "X X X X X \n" + "X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X \n" + "X X X X X X \n" + "X X X X X X X X X X X X X X X X X \n" + " X X X \n" + " X X X X X X X \n" + "X X X X X X X X \n" + "X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + " X X X X X X X X X X \n" + " X X X X X X X X \n" + "X X X X X X X X X X X X X X X \n" + "X X X X X X X \n" + "X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X \n" + "X X X X X X X X X X \n" + "X X X X X X X \n" + "X X X X X X X X X X X X X \n"); +} + +TEST(QREncoderTest, EncodeKanjiMode) +{ + auto qrCode = Encode(L"\u65e5\u672c", ErrorCorrectionLevel::Medium, CharacterSet::Shift_JIS, 0, false, -1); + EXPECT_EQ(qrCode.mode, CodecMode::KANJI); + EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::Medium); + ASSERT_NE(qrCode.version, nullptr); + EXPECT_EQ(qrCode.version->versionNumber(), 1); + EXPECT_EQ(qrCode.maskPattern, 0); + EXPECT_EQ(ToString(qrCode.matrix), + "X X X X X X X X X X X X X X X X \n" + "X X X X X X \n" + "X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X \n" + "X X X X X X X \n" + "X X X X X X X X X X X X X X X X X \n" + " X \n" + "X X X X X X X X \n" + "X X X X X X X X X X \n" + " X X X X X X X X X X X X \n" + "X X X X X X X X X X X \n" + " X X X X X X X X X X X X \n" + " X X X X X \n" + "X X X X X X X X X X X \n" + "X X X X X X X \n" + "X X X X X X X X X X X \n" + "X X X X X X X X X X \n" + "X X X X X X X X X X X X X \n" + "X X X X X X X X X \n" + "X X X X X X X X X X X X X X \n"); +} + +TEST(QREncoderTest, EncodeShiftjisNumeric) +{ + auto qrCode = Encode(L"0123", ErrorCorrectionLevel::Medium, CharacterSet::Shift_JIS, 0, false, -1); + EXPECT_EQ(qrCode.mode, CodecMode::NUMERIC); + EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::Medium); + ASSERT_NE(qrCode.version, nullptr); + EXPECT_EQ(qrCode.version->versionNumber(), 1); + EXPECT_EQ(qrCode.maskPattern, 2); + EXPECT_EQ(ToString(qrCode.matrix), + "X X X X X X X X X X X X X X X X X \n" + "X X X X X X \n" + "X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X \n" + "X X X X X X X \n" + "X X X X X X X X X X X X X X X X X \n" + " X X X X X \n" + "X X X X X X X X X X X X X X \n" + "X X X X X X X X \n" + " X X X X X X X X X X X X X X \n" + "X X X X X X X X X \n" + " X X X X X X X X \n" + " X X X X X X X \n" + "X X X X X X X X X X X \n" + "X X X X X X X X X X \n" + "X X X X X X X X X X \n" + "X X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X X X \n" + "X X X X X X X X X X X X X X \n"); +} + +TEST(QREncoderTest, EncodeGS1) +{ + auto qrCode = Encode(L"100001%11171218", ErrorCorrectionLevel::High, CharacterSet::Unknown, 0, true, -1); + EXPECT_EQ(qrCode.mode, CodecMode::ALPHANUMERIC); + EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::High); + ASSERT_NE(qrCode.version, nullptr); + EXPECT_EQ(qrCode.version->versionNumber(), 2); + EXPECT_EQ(qrCode.maskPattern, 4); + EXPECT_EQ(ToString(qrCode.matrix), + "X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X \n" + "X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X \n" + "X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X \n" + " X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X \n" + "X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X \n" + "X X X X X X X X X X X X X X \n" + " X X X X X X X X \n" + "X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X \n"); +} + +TEST(QREncoderTest, EncodeGS1ModeHeaderWithECI) +{ + auto qrCode = Encode(L"hello", ErrorCorrectionLevel::High, CharacterSet::UTF8, 0, true, -1); + EXPECT_EQ(qrCode.mode, CodecMode::BYTE); + EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::High); + ASSERT_NE(qrCode.version, nullptr); + EXPECT_EQ(qrCode.version->versionNumber(), 1); + EXPECT_EQ(qrCode.maskPattern, 5); + EXPECT_EQ(ToString(qrCode.matrix), + "X X X X X X X X X X X X X X X X X \n" + "X X X X X X \n" + "X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X \n" + " X X X X \n" + " X X X X X X X X \n" + " X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X X X X X X \n" + " X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X X X X \n" + "X X X X X X X X X X \n" + "X X X X X X X X X X X X \n" + "X X X X X X X X X X X \n" + "X X X X X X X X X \n" + "X X X X X X X X X X X X X X \n"); +} + +TEST(QREncoderTest, AppendModeInfo) +{ + BitArray bits; + AppendModeInfo(CodecMode::NUMERIC, bits); + EXPECT_EQ(ToString(bits), "...X"); +} + +TEST(QREncoderTest, AppendLengthInfo) +{ + BitArray bits; + AppendLengthInfo(1, // 1 letter (1/1). + *Version::FromNumber(1), CodecMode::NUMERIC, bits); + EXPECT_EQ(ToString(bits), RemoveSpace("........ .X")); // 10 bits. + + bits = BitArray(); + AppendLengthInfo(2, // 2 letters (2/1). + *Version::FromNumber(10), CodecMode::ALPHANUMERIC, bits); + EXPECT_EQ(ToString(bits), RemoveSpace("........ .X.")); // 11 bits. + + bits = BitArray(); + AppendLengthInfo(255, // 255 letter (255/1). + *Version::FromNumber(27), CodecMode::BYTE, bits); + EXPECT_EQ(ToString(bits), RemoveSpace("........ XXXXXXXX")); // 16 bits. + + bits = BitArray(); + AppendLengthInfo(512, // 512 letters (1024/2). + *Version::FromNumber(40), CodecMode::KANJI, bits); + EXPECT_EQ(ToString(bits), RemoveSpace("..X..... ....")); // 12 bits. +} + +TEST(QREncoderTest, AppendNumericBytes) +{ + // 1 = 01 = 0001 in 4 bits. + BitArray bits; + AppendNumericBytes(L"1", bits); + EXPECT_EQ(ToString(bits), RemoveSpace("...X")); + + // 12 = 0xc = 0001100 in 7 bits. + bits = BitArray(); + AppendNumericBytes(L"12", bits); + EXPECT_EQ(ToString(bits), RemoveSpace("...XX..")); + + // 123 = 0x7b = 0001111011 in 10 bits. + bits = BitArray(); + AppendNumericBytes(L"123", bits); + EXPECT_EQ(ToString(bits), RemoveSpace("...XXXX. XX")); + + // 1234 = "123" + "4" = 0001111011 + 0100 + bits = BitArray(); + AppendNumericBytes(L"1234", bits); + EXPECT_EQ(ToString(bits), RemoveSpace("...XXXX. XX.X..")); + + // Empty. + bits = BitArray(); + AppendNumericBytes(L"", bits); + EXPECT_EQ(ToString(bits), RemoveSpace("")); +} + +TEST(QREncoderTest, AppendAlphanumericBytes) +{ + // A = 10 = 0xa = 001010 in 6 bits + BitArray bits; + AppendAlphanumericBytes(L"A", bits); + EXPECT_EQ(ToString(bits), RemoveSpace("..X.X.")); + + // AB = 10 * 45 + 11 = 461 = 0x1cd = 00111001101 in 11 bits + bits = BitArray(); + AppendAlphanumericBytes(L"AB", bits); + EXPECT_EQ(ToString(bits), RemoveSpace("..XXX..X X.X")); + + // ABC = "AB" + "C" = 00111001101 + 001100 + bits = BitArray(); + AppendAlphanumericBytes(L"ABC", bits); + EXPECT_EQ(ToString(bits), RemoveSpace("..XXX..X X.X..XX. .")); + + // Empty. + bits = BitArray(); + AppendAlphanumericBytes(L"", bits); + EXPECT_EQ(ToString(bits), RemoveSpace("")); + + // Invalid data. + EXPECT_THROW(AppendAlphanumericBytes(L"abc", bits), std::invalid_argument); +} + +TEST(QREncoderTest, Append8BitBytes) +{ + // 0x61, 0x62, 0x63 + BitArray bits; + Append8BitBytes(L"abc", CharacterSet::Unknown, bits); + EXPECT_EQ(ToString(bits), RemoveSpace(".XX....X .XX...X. .XX...XX")); + + // Empty. + bits = BitArray(); + Append8BitBytes(L"", CharacterSet::Unknown, bits); + EXPECT_EQ(ToString(bits), RemoveSpace("")); +} + +// Numbers are from page 21 of JISX0510:2004 +TEST(QREncoderTest, AppendKanjiBytes) +{ + BitArray bits; + AppendKanjiBytes(ShiftJISString({ 0x93, 0x5f }), bits); + EXPECT_EQ(ToString(bits), RemoveSpace(".XX.XX.. XXXXX")); + + AppendKanjiBytes(ShiftJISString({ 0xe4, 0xaa }), bits); + EXPECT_EQ(ToString(bits), RemoveSpace(".XX.XX.. XXXXXXX. X.X.X.X. X.")); +} + +TEST(QREncoderTest, AppendBytes) +{ + // Should use appendNumericBytes. + // 1 = 01 = 0001 in 4 bits. + BitArray bits; + AppendBytes(L"1", CodecMode::NUMERIC, CharacterSet::Unknown, bits); + EXPECT_EQ(ToString(bits), RemoveSpace("...X")); + + // Should use appendAlphanumericBytes. + // A = 10 = 0xa = 001010 in 6 bits + bits = BitArray(); + AppendBytes(L"A", CodecMode::ALPHANUMERIC, CharacterSet::Unknown, bits); + EXPECT_EQ(ToString(bits), RemoveSpace("..X.X.")); + + // Lower letters such as 'a' cannot be encoded in MODE_ALPHANUMERIC. + bits = BitArray(); + EXPECT_THROW(AppendBytes(L"a", CodecMode::ALPHANUMERIC, CharacterSet::Unknown, bits), std::invalid_argument); + + // Should use append8BitBytes. + // 0x61, 0x62, 0x63 + bits = BitArray(); + AppendBytes(L"abc", CodecMode::BYTE, CharacterSet::Unknown, bits); + EXPECT_EQ(ToString(bits), RemoveSpace(".XX....X .XX...X. .XX...XX")); + + // Anything can be encoded in QRCode.MODE_8BIT_BYTE. + AppendBytes(L"\0", CodecMode::BYTE, CharacterSet::Unknown, bits); + + // Should use appendKanjiBytes. + // 0x93, 0x5f + bits = BitArray(); + AppendBytes(ShiftJISString({0x93, 0x5f}), CodecMode::KANJI, CharacterSet::Unknown, bits); + EXPECT_EQ(ToString(bits), RemoveSpace(".XX.XX.. XXXXX")); +} + +TEST(QREncoderTest, TerminateBits) +{ + BitArray v; + TerminateBits(0, v); + EXPECT_EQ(ToString(v), RemoveSpace("")); + + v = BitArray(); + TerminateBits(1, v); + EXPECT_EQ(ToString(v), RemoveSpace("........")); + + v = BitArray(); + v.appendBits(0, 3); + TerminateBits(1, v); + EXPECT_EQ(ToString(v), RemoveSpace("........")); + + v = BitArray(); + v.appendBits(0, 5); + TerminateBits(1, v); + EXPECT_EQ(ToString(v), RemoveSpace("........")); + + v = BitArray(); + v.appendBits(0, 8); + TerminateBits(1, v); + EXPECT_EQ(ToString(v), RemoveSpace("........")); + + v = BitArray(); + TerminateBits(2, v); + EXPECT_EQ(ToString(v), RemoveSpace("........ XXX.XX..")); + + v = BitArray(); + v.appendBits(0, 1); + TerminateBits(3, v); + EXPECT_EQ(ToString(v), RemoveSpace("........ XXX.XX.. ...X...X")); +} + +TEST(QREncoderTest, GetNumDataBytesAndNumECBytesForBlockID) +{ + int numDataBytes; + int numEcBytes; + // Version 1-H. + GetNumDataBytesAndNumECBytesForBlockID(26, 9, 1, 0, numDataBytes, numEcBytes); + EXPECT_EQ(9, numDataBytes); + EXPECT_EQ(17, numEcBytes); + + // Version 3-H. 2 blocks. + GetNumDataBytesAndNumECBytesForBlockID(70, 26, 2, 0, numDataBytes, numEcBytes); + EXPECT_EQ(13, numDataBytes); + EXPECT_EQ(22, numEcBytes); + GetNumDataBytesAndNumECBytesForBlockID(70, 26, 2, 1, numDataBytes, numEcBytes); + EXPECT_EQ(13, numDataBytes); + EXPECT_EQ(22, numEcBytes); + + // Version 7-H. (4 + 1) blocks. + GetNumDataBytesAndNumECBytesForBlockID(196, 66, 5, 0, numDataBytes, numEcBytes); + EXPECT_EQ(13, numDataBytes); + EXPECT_EQ(26, numEcBytes); + GetNumDataBytesAndNumECBytesForBlockID(196, 66, 5, 4, numDataBytes, numEcBytes); + EXPECT_EQ(14, numDataBytes); + EXPECT_EQ(26, numEcBytes); + + // Version 40-H. (20 + 61) blocks. + GetNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 0, numDataBytes, numEcBytes); + EXPECT_EQ(15, numDataBytes); + EXPECT_EQ(30, numEcBytes); + GetNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 20, numDataBytes, numEcBytes); + EXPECT_EQ(16, numDataBytes); + EXPECT_EQ(30, numEcBytes); + GetNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 80, numDataBytes, numEcBytes); + EXPECT_EQ(16, numDataBytes); + EXPECT_EQ(30, numEcBytes); +} + +// Numbers are from http://www.swetake.com/qr/qr3.html and +// http://www.swetake.com/qr/qr9.html +TEST(QREncoderTest, GenerateECBytes) +{ + ByteArray ecBytes; + GenerateECBytes({ 32, 65, 205, 69, 41, 220, 46, 128, 236 }, 17, ecBytes); + ByteArray expected = { 42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61 }; + EXPECT_EQ(ecBytes, expected); + + GenerateECBytes({ 67, 70, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214 }, 18, ecBytes); + expected = { 175, 80, 155, 64, 178, 45, 214, 233, 65, 209, 12, 155, 117, 31, 140, 214, 27, 187 }; + EXPECT_EQ(ecBytes, expected); + + // High-order zero coefficient case. + GenerateECBytes({ 32, 49, 205, 69, 42, 20, 0, 236, 17 }, 17, ecBytes); + expected = { 0, 3, 130, 179, 194, 0, 55, 211, 110, 79, 98, 72, 170, 96, 211, 137, 213 }; + EXPECT_EQ(ecBytes, expected); +} + +TEST(QREncoderTest, InterleaveWithECBytes) +{ + BitArray in; + for (int dataByte : {32, 65, 205, 69, 41, 220, 46, 128, 236}) + in.appendBits(dataByte, 8); + + BitArray out = InterleaveWithECBytes(in, 26, 9, 1); + std::vector expected = { + 32, 65, 205, 69, 41, 220, 46, 128, 236, + + 42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61, + }; // Error correction bytes in second block + ASSERT_EQ(Size(expected), out.sizeInBytes()); + EXPECT_EQ(expected, out.toBytes()); + + // Numbers are from http://www.swetake.com/qr/qr8.html + in = BitArray(); + for (int dataByte : + {67, 70, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214, 230, 247, 7, 23, 39, 55, + 71, 87, 103, 119, 135, 151, 166, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214, 230, + 247, 7, 23, 39, 55, 71, 87, 103, 119, 135, 151, 160, 236, 17, 236, 17, 236, 17, 236, 17}) + in.appendBits(dataByte, 8); + + out = InterleaveWithECBytes(in, 134, 62, 4); + expected = { + 67, 230, 54, 55, 70, 247, 70, 71, 22, 7, 86, 87, 38, 23, 102, 103, 54, 39, 118, 119, 70, + 55, 134, 135, 86, 71, 150, 151, 102, 87, 166, 160, 118, 103, 182, 236, 134, 119, 198, 17, 150, 135, + 214, 236, 166, 151, 230, 17, 182, 166, 247, 236, 198, 22, 7, 17, 214, 38, 23, 236, 39, 17, + + 175, 155, 245, 236, 80, 146, 56, 74, 155, 165, 133, 142, 64, 183, 132, 13, 178, 54, 132, 108, 45, + 113, 53, 50, 214, 98, 193, 152, 233, 147, 50, 71, 65, 190, 82, 51, 209, 199, 171, 54, 12, 112, + 57, 113, 155, 117, 211, 164, 117, 30, 158, 225, 31, 190, 242, 38, 140, 61, 179, 154, 214, 138, 147, + 87, 27, 96, 77, 47, 187, 49, 156, 214, + }; // Error correction bytes in second block + EXPECT_EQ(Size(expected), out.sizeInBytes()); + EXPECT_EQ(expected, out.toBytes()); +} + +TEST(QREncoderTest, BugInBitVectorNumBytes) +{ + // There was a bug in BitVector.sizeInBytes() that caused it to return a + // smaller-by-one value (ex. 1465 instead of 1466) if the number of bits + // in the vector is not 8-bit aligned. In QRCodeEncoder::InitQRCode(), + // BitVector::sizeInBytes() is used for finding the smallest QR Code + // version that can fit the given data. Hence there were corner cases + // where we chose a wrong QR Code version that cannot fit the given + // data. Note that the issue did not occur with MODE_8BIT_BYTE, as the + // bits in the bit vector are always 8-bit aligned. + // + // Before the bug was fixed, the following test didn't pass, because: + // + // - MODE_NUMERIC is chosen as all bytes in the data are '0' + // - The 3518-byte numeric data needs 1466 bytes + // - 3518 / 3 * 10 + 7 = 11727 bits = 1465.875 bytes + // - 3 numeric bytes are encoded in 10 bits, hence the first + // 3516 bytes are encoded in 3516 / 3 * 10 = 11720 bits. + // - 2 numeric bytes can be encoded in 7 bits, hence the last + // 2 bytes are encoded in 7 bits. + // - The version 27 QR Code with the EC level L has 1468 bytes for data. + // - 1828 - 360 = 1468 + // - In InitQRCode(), 3 bytes are reserved for a header. Hence 1465 bytes + // (1468 -3) are left for data. + // - Because of the bug in BitVector::sizeInBytes(), InitQRCode() determines + // the given data can fit in 1465 bytes, despite it needs 1466 bytes. + // - Hence QRCodeEncoder.encode() failed and returned false. + // - To be precise, it needs 11727 + 4 (getMode info) + 14 (length info) = + // 11745 bits = 1468.125 bytes are needed (i.e. cannot fit in 1468 + // bytes). + Encode(std::wstring(3518, L'0'), ErrorCorrectionLevel::Low, CharacterSet::Unknown, 0, false, -1); +} diff --git a/src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.cpp b/src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.cpp new file mode 100644 index 0000000..77c1030 --- /dev/null +++ b/src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.cpp @@ -0,0 +1,45 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "qrcode/QRErrorCorrectionLevel.h" + +#include "gtest/gtest.h" + +using namespace ZXing; +using namespace ZXing::QRCode; + +TEST(QRErrorCorrectionLevelTest, ForBits) +{ + EXPECT_EQ(ErrorCorrectionLevel::Medium, ECLevelFromBits(0)); + EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(1)); + EXPECT_EQ(ErrorCorrectionLevel::High, ECLevelFromBits(2)); + EXPECT_EQ(ErrorCorrectionLevel::Quality, ECLevelFromBits(3)); +} + +TEST(QRErrorCorrectionLevelTest, ForMicroBits) +{ + EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(0, true)); + EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(1, true)); + EXPECT_EQ(ErrorCorrectionLevel::Medium, ECLevelFromBits(2, true)); + EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(3, true)); + EXPECT_EQ(ErrorCorrectionLevel::Medium, ECLevelFromBits(4, true)); + EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(5, true)); + EXPECT_EQ(ErrorCorrectionLevel::Medium, ECLevelFromBits(6, true)); + EXPECT_EQ(ErrorCorrectionLevel::Quality, ECLevelFromBits(7, true)); + + EXPECT_EQ(ErrorCorrectionLevel::Quality, ECLevelFromBits(-1, true)); + EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(8, true)); +} + +TEST(QRErrorCorrectionLevelTest, ToString) +{ + using namespace std::literals; + + EXPECT_EQ("L"s, ToString(ErrorCorrectionLevel::Low)); + EXPECT_EQ("M"s, ToString(ErrorCorrectionLevel::Medium)); + EXPECT_EQ("Q"s, ToString(ErrorCorrectionLevel::Quality)); + EXPECT_EQ("H"s, ToString(ErrorCorrectionLevel::High)); +} diff --git a/src/qrcode/cpp_port/test/QRFormatInformationTest.rs b/src/qrcode/cpp_port/test/QRFormatInformationTest.rs new file mode 100644 index 0000000..0d2a867 --- /dev/null +++ b/src/qrcode/cpp_port/test/QRFormatInformationTest.rs @@ -0,0 +1,124 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2007 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +use crate::qrcode::decoder::{ErrorCorrectionLevel, FormatInformation}; + +const MASKED_TEST_FORMAT_INFO: u32 = 0x2BED; +const MASKED_TEST_FORMAT_INFO2: u32 = + ((0x2BED << 1) & 0b1111111000000000) | 0b100000000 | (0x2BED & 0b11111111); // insert the 'Dark Module' +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; + +fn DoFormatInformationTest(formatInfo: u32, expectedMask: u8, expectedECL: ErrorCorrectionLevel) { + let parsedFormat = FormatInformation::DecodeMQR(formatInfo); + assert!(parsedFormat.isValid()); + assert_eq!(expectedMask, parsedFormat.data_mask); + assert_eq!(expectedECL, parsedFormat.error_correction_level); +} + +#[test] +fn Decode() { + // Normal case + let expected = FormatInformation::DecodeQR(MASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2); + assert!(expected.isValid()); + assert_eq!(0x07, expected.data_mask); + assert_eq!(ErrorCorrectionLevel::Q, expected.error_correction_level); + // where the code forgot the mask! + assert_eq!( + expected, + FormatInformation::DecodeQR(UNMASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2) + ); +} + +#[test] +fn DecodeWithBitDifference() { + let expected = FormatInformation::DecodeQR(MASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2); + // 1,2,3,4 bits difference + assert_eq!( + expected, + FormatInformation::DecodeQR( + MASKED_TEST_FORMAT_INFO ^ 0x01, + MASKED_TEST_FORMAT_INFO2 ^ 0x01 + ) + ); + assert_eq!( + expected, + FormatInformation::DecodeQR( + MASKED_TEST_FORMAT_INFO ^ 0x03, + MASKED_TEST_FORMAT_INFO2 ^ 0x03 + ) + ); + assert_eq!( + expected, + FormatInformation::DecodeQR( + MASKED_TEST_FORMAT_INFO ^ 0x07, + MASKED_TEST_FORMAT_INFO2 ^ 0x07 + ) + ); + assert!(!FormatInformation::DecodeQR( + MASKED_TEST_FORMAT_INFO ^ 0x0F, + MASKED_TEST_FORMAT_INFO2 ^ 0x0F + ) + .isValid()); +} + +#[test] +fn DecodeWithMisread() { + let expected = FormatInformation::DecodeQR(MASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2); + assert_eq!( + expected, + FormatInformation::DecodeQR( + MASKED_TEST_FORMAT_INFO ^ 0x03, + MASKED_TEST_FORMAT_INFO2 ^ 0x0F + ) + ); +} + +#[test] +fn DecodeMicro() { + // Normal cases. + DoFormatInformationTest(0x4445, 0x0, ErrorCorrectionLevel::L); + DoFormatInformationTest(0x4172, 0x1, ErrorCorrectionLevel::L); + DoFormatInformationTest(0x5fc0, 0x2, ErrorCorrectionLevel::L); + DoFormatInformationTest(0x5af7, 0x3, ErrorCorrectionLevel::L); + DoFormatInformationTest(0x6793, 0x0, ErrorCorrectionLevel::M); + DoFormatInformationTest(0x62a4, 0x1, ErrorCorrectionLevel::M); + DoFormatInformationTest(0x3e8d, 0x2, ErrorCorrectionLevel::Q); + DoFormatInformationTest(MICRO_MASKED_TEST_FORMAT_INFO, 0x3, ErrorCorrectionLevel::Q); + + // where the code forgot the mask! + // DoFormatInformationTest(MICRO_UNMASKED_TEST_FORMAT_INFO, 0x3, ErrorCorrectionLevel::Quality); +} + +// This doesn't work as expected because the implementation of the decode tries with +// and without the mask (0x4445). This effectively adds a tolerance of 5 bits to the Hamming +// distance calculation. +#[test] +fn DecodeMicroWithBitDifference() { + let expected = FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO); + + // 1,2,3 bits difference + assert_eq!( + expected, + FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x01) + ); + assert_eq!( + expected, + FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x03) + ); + assert_eq!( + expected, + FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x07) + ); + + // Bigger bit differences can return valid FormatInformation objects but the data mask and error + // correction levels do not match. + // EXPECT_TRUE(FormatInformation::DecodeFormatInformation(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x3f).isValid()); + // EXPECT_NE(expected.dataMask(), FormatInformation::DecodeFormatInformation(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x3f).dataMask()); + // EXPECT_NE(expected.errorCorrectionLevel(), + // FormatInformation::DecodeFormatInformation(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x3f).errorCorrectionLevel()); +} diff --git a/src/qrcode/cpp_port/test/QRModeTest.rs b/src/qrcode/cpp_port/test/QRModeTest.rs new file mode 100644 index 0000000..fa356a0 --- /dev/null +++ b/src/qrcode/cpp_port/test/QRModeTest.rs @@ -0,0 +1,135 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +use crate::qrcode::decoder::{Mode, Version}; + +#[test] +fn ForBits() { + assert_eq!( + Mode::TERMINATOR, + Mode::CodecModeForBits(0x00, None).unwrap() + ); + assert_eq!(Mode::NUMERIC, Mode::CodecModeForBits(0x01, None).unwrap()); + assert_eq!( + Mode::ALPHANUMERIC, + Mode::CodecModeForBits(0x02, None).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()); +} + +#[test] +fn CharacterCount() { + // Spot check a few values + assert_eq!( + 10, + Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(5, false).unwrap()) + ); + assert_eq!( + 12, + Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(26, false).unwrap()) + ); + assert_eq!( + 14, + Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(40, false).unwrap()) + ); + assert_eq!( + 9, + Mode::CharacterCountBits(&Mode::ALPHANUMERIC, Version::FromNumber(6, false).unwrap()) + ); + assert_eq!( + 8, + Mode::CharacterCountBits(&Mode::BYTE, Version::FromNumber(7, false).unwrap()) + ); + assert_eq!( + 8, + Mode::CharacterCountBits(&Mode::KANJI, Version::FromNumber(8, false).unwrap()) + ); +} + +#[test] +fn MicroForBits() { + // M1 + assert_eq!( + Mode::NUMERIC, + Mode::CodecModeForBits(0x00, Some(true)).unwrap() + ); + // M2 + assert_eq!( + Mode::NUMERIC, + Mode::CodecModeForBits(0x00, Some(true)).unwrap() + ); + assert_eq!( + Mode::ALPHANUMERIC, + Mode::CodecModeForBits(0x01, Some(true)).unwrap() + ); + // M3 + assert_eq!( + Mode::NUMERIC, + Mode::CodecModeForBits(0x00, Some(true)).unwrap() + ); + assert_eq!( + Mode::ALPHANUMERIC, + Mode::CodecModeForBits(0x01, Some(true)).unwrap() + ); + assert_eq!( + Mode::BYTE, + Mode::CodecModeForBits(0x02, Some(true)).unwrap() + ); + assert_eq!( + Mode::KANJI, + Mode::CodecModeForBits(0x03, Some(true)).unwrap() + ); + // M4 + assert_eq!( + Mode::NUMERIC, + Mode::CodecModeForBits(0x00, Some(true)).unwrap() + ); + assert_eq!( + Mode::ALPHANUMERIC, + Mode::CodecModeForBits(0x01, Some(true)).unwrap() + ); + assert_eq!( + Mode::BYTE, + Mode::CodecModeForBits(0x02, Some(true)).unwrap() + ); + assert_eq!( + Mode::KANJI, + Mode::CodecModeForBits(0x03, Some(true)).unwrap() + ); + + assert!(Mode::CodecModeForBits(0x04, Some(true)).is_err()); +} + +#[test] +fn MicroCharacterCount() { + // Spot check a few values + assert_eq!( + 3, + Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(1, true).unwrap()) + ); + assert_eq!( + 4, + Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(2, true).unwrap()) + ); + assert_eq!( + 6, + Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(4, true).unwrap()) + ); + assert_eq!( + 3, + Mode::CharacterCountBits(&Mode::ALPHANUMERIC, Version::FromNumber(2, true).unwrap()) + ); + assert_eq!( + 4, + Mode::CharacterCountBits(&Mode::BYTE, Version::FromNumber(3, true).unwrap()) + ); + assert_eq!( + 4, + Mode::CharacterCountBits(&Mode::KANJI, Version::FromNumber(4, true).unwrap()) + ); +} diff --git a/src/qrcode/cpp_port/test/QRVersionTest.rs b/src/qrcode/cpp_port/test/QRVersionTest.rs new file mode 100644 index 0000000..0f79ecf --- /dev/null +++ b/src/qrcode/cpp_port/test/QRVersionTest.rs @@ -0,0 +1,122 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + common::BitMatrix, + qrcode::decoder::{Version, VersionRef}, +}; + +fn CheckVersion(version: VersionRef, number: u32, dimension: u32) { + // assert_ne!(version, nullptr); + assert_eq!(number, version.getVersionNumber()); + if (number > 1 && !version.isMicroQRCode()) { + assert!(!version.getAlignmentPatternCenters().is_empty()); + } + assert_eq!(dimension, version.getDimensionForVersion()); +} + +fn DoTestVersion(expectedVersion: u32, mask: i32) { + let version = Version::DecodeVersionInformation(mask, 0).expect("should exist"); + // assert_ne!(version, nullptr); + assert_eq!(expectedVersion, version.getVersionNumber()); +} + +#[test] +fn VersionForNumber() { + let version = Version::FromNumber(0, false); + assert!(version.is_err(), "There is version with number 0"); + + for i in 1..=40 { + // for (int i = 1; i <= 40; i++) { + CheckVersion( + Version::FromNumber(i, false).expect("version number found"), + i, + 4 * i + 17, + ); + } +} + +#[test] +fn GetProvisionalVersionForDimension() { + for i in 1..=40 { + // for (int i = 1; i <= 40; i++) { + let prov = + Version::FromDimension(4 * i + 17).expect(&format!("version should exist for {i}")); + // assert_ne!(prov, nullptr); + assert_eq!(i, prov.getVersionNumber()); + } +} + +#[test] +fn DecodeVersionInformation() { + // Spot check + DoTestVersion(7, 0x07C94); + DoTestVersion(12, 0x0C762); + DoTestVersion(17, 0x1145D); + DoTestVersion(22, 0x168C9); + DoTestVersion(27, 0x1B08E); + DoTestVersion(32, 0x209D5); +} + +#[test] +fn MicroVersionForNumber() { + let version = Version::FromNumber(0, true); + assert!(version.is_err(), "There is version with number 0"); + + for i in 1..=4 { + // for (int i = 1; i <= 4; i++) { + CheckVersion( + Version::FromNumber(i, true).expect(&format!("version for {i} should exist")), + i, + 2 * i + 9, + ); + } +} + +#[test] +fn GetProvisionalMicroVersionForDimension() { + for i in 1..=4 { + // for (int i = 1; i <= 4; i++) { + let prov = Version::FromDimension(2 * i + 9) + .expect(&format!("version for micro {i} should exist")); + // assert_ne!(prov, nullptr); + assert_eq!(i, prov.getVersionNumber()); + } +} + +#[test] +fn FunctionPattern() { + let testFinderPatternRegion = |bitMatrix: &BitMatrix| { + for row in 0..9 { + // for (int row = 0; row < 9; row++){ + for col in 0..9 { + // for (int col = 0; col < 9; col++) { + assert!(bitMatrix.get(col, row)); + } + } + }; + for i in 1..=4 { + // for (int i = 1; i <= 4; i++) { + let version = Version::FromNumber(i, true).expect("version must be found"); + let functionPattern = version + .buildFunctionPattern() + .expect("function pattern must be found"); + testFinderPatternRegion(&functionPattern); + + // Check timing pattern areas. + let dimension = version.getDimensionForVersion(); + for row in dimension..functionPattern.height() + // for (int row = dimension; row < functionPattern.height(); row++) + { + assert!(functionPattern.get(0, row)); + } + for col in dimension..functionPattern.width() + // for (int col = dimension; col < functionPattern.width(); col++) + { + assert!(functionPattern.get(col, 0)); + } + } +} diff --git a/src/qrcode/cpp_port/test/QRWriterTest.cpp b/src/qrcode/cpp_port/test/QRWriterTest.cpp new file mode 100644 index 0000000..bb33cf4 --- /dev/null +++ b/src/qrcode/cpp_port/test/QRWriterTest.cpp @@ -0,0 +1,160 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "BitMatrixIO.h" +#include "qrcode/QRWriter.h" +#include "qrcode/QRErrorCorrectionLevel.h" + +#include "gtest/gtest.h" +#include + +using namespace ZXing; +using namespace ZXing::QRCode; + +namespace { + + void DoTest(const std::wstring& contents, ErrorCorrectionLevel ecLevel, int resolution, const char* expected) { + Writer writer; + writer.setErrorCorrectionLevel(ecLevel); + auto matrix = writer.encode(contents, resolution, resolution); + auto actual = ToString(matrix, 'X', ' ', true); + EXPECT_EQ(matrix.width(), resolution); + EXPECT_EQ(matrix.height(), resolution); + EXPECT_EQ(actual, expected); + } + +} + +TEST(QRWriterTest, OverSize) +{ + // The QR should be multiplied up to fit, with extra padding if necessary + int bigEnough = 256; + Writer writer; + BitMatrix matrix = writer.encode(L"http://www.google.com/", bigEnough, bigEnough); + EXPECT_EQ(matrix.width(), bigEnough); + EXPECT_EQ(matrix.height(), bigEnough); + + // The QR will not fit in this size, so the matrix should come back bigger + int tooSmall = 20; + matrix = writer.encode(L"http://www.google.com/", tooSmall, tooSmall); + EXPECT_GT(matrix.width(), tooSmall); + EXPECT_GT(matrix.height(), tooSmall); + + // We should also be able to handle non-square requests by padding them + int strangeWidth = 500; + int strangeHeight = 100; + matrix = writer.encode(L"http://www.google.com/", strangeWidth, strangeHeight); + EXPECT_EQ(matrix.width(), strangeWidth); + EXPECT_EQ(matrix.height(), strangeHeight); + + int overflowSize = 0x1000000; + EXPECT_THROW(writer.encode(L"http://www.google.com/", overflowSize, overflowSize), std::invalid_argument); +} + +TEST(QRWriterTest, RegressionTest) +{ + DoTest(L"http://www.google.com/", ErrorCorrectionLevel::Medium, 99, + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X \n" + " X X X X X X X X X X X X \n" + " X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); +} diff --git a/src/qrcode/cpp_port/test/mod.rs b/src/qrcode/cpp_port/test/mod.rs new file mode 100644 index 0000000..4aaae93 --- /dev/null +++ b/src/qrcode/cpp_port/test/mod.rs @@ -0,0 +1,4 @@ +mod QRModeTest; +mod QRVersionTest; + +mod QRFormatInformationTest; diff --git a/src/qrcode/decoder/version.rs b/src/qrcode/decoder/version.rs index 98a1ab2..c4edf30 100755 --- a/src/qrcode/decoder/version.rs +++ b/src/qrcode/decoder/version.rs @@ -108,7 +108,8 @@ impl Version { } pub fn getDimensionForVersion(&self) -> u32 { - 17 + 4 * self.versionNumber + Self::DimensionOfVersion(self.versionNumber, self.is_micro) + // 17 + 4 * self.versionNumber } pub fn getECBlocksForLevel(&self, ecLevel: ErrorCorrectionLevel) -> &ECBlocks { @@ -171,33 +172,42 @@ impl Version { // Top left finder pattern + separator + format bitMatrix.setRegion(0, 0, 9, 9)?; - // Top right finder pattern + separator + format - bitMatrix.setRegion(dimension - 8, 0, 8, 9)?; - // Bottom left finder pattern + separator + format - bitMatrix.setRegion(0, dimension - 8, 9, 8)?; - // Alignment patterns - let max = self.alignmentPatternCenters.len(); - for x in 0..max { - let i = self.alignmentPatternCenters[x] - 2; - for y in 0..max { - if (x != 0 || (y != 0 && y != max - 1)) && (x != max - 1 || y != 0) { - bitMatrix.setRegion(self.alignmentPatternCenters[y] - 2, i, 5, 5)?; + if !self.is_micro { + // Top right finder pattern + separator + format + bitMatrix.setRegion(dimension - 8, 0, 8, 9)?; + // Bottom left finder pattern + separator + format + bitMatrix.setRegion(0, dimension - 8, 9, 8)?; + + // Alignment patterns + let max = self.alignmentPatternCenters.len(); + for x in 0..max { + let i = self.alignmentPatternCenters[x] - 2; + for y in 0..max { + if (x != 0 || (y != 0 && y != max - 1)) && (x != max - 1 || y != 0) { + bitMatrix.setRegion(self.alignmentPatternCenters[y] - 2, i, 5, 5)?; + } + // else no o alignment patterns near the three finder patterns } - // else no o alignment patterns near the three finder patterns } - } - // Vertical timing pattern - bitMatrix.setRegion(6, 9, 1, dimension - 17)?; - // Horizontal timing pattern - bitMatrix.setRegion(9, 6, dimension - 17, 1)?; + // Vertical timing pattern + bitMatrix.setRegion(6, 9, 1, dimension - 17)?; + // Horizontal timing pattern + bitMatrix.setRegion(9, 6, dimension - 17, 1)?; - if self.versionNumber > 6 { - // Version info, top right - bitMatrix.setRegion(dimension - 11, 0, 3, 6)?; - // Version info, bottom left - bitMatrix.setRegion(0, dimension - 11, 6, 3)?; + if self.versionNumber > 6 { + // Version info, top right + bitMatrix.setRegion(dimension - 11, 0, 3, 6)?; + // Version info, bottom left + bitMatrix.setRegion(0, dimension - 11, 6, 3)?; + } + } else { + // Vertical timing pattern + bitMatrix.setRegion(9, 0, dimension - 9, 1)?; + + // Horizontal timing pattern + bitMatrix.setRegion(0, 9, 1, dimension - 9)?; } Ok(bitMatrix) From 1900e1ca576dfac1529116823324a05143fb7de1 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sun, 9 Apr 2023 18:25:59 -0500 Subject: [PATCH 46/72] continued porting of test cases --- .../base_extentions/qr_ec_level.rs | 6 +- .../base_extentions/qr_formatinformation.rs | 5 + src/common/cpp_essentials/decoder_result.rs | 29 +++ src/common/eci_string_builder.rs | 2 +- .../test/QRDecodedBitStreamParserTest.cpp | 134 ------------ .../test/QRDecodedBitStreamParserTest.rs | 190 ++++++++++++++++++ .../test/QRErrorCorrectionLevelTest.cpp | 45 ----- .../test/QRErrorCorrectionLevelTest.rs | 89 ++++++++ .../cpp_port/test/QRFormatInformationTest.rs | 74 ++++--- src/qrcode/cpp_port/test/mod.rs | 4 + 10 files changed, 364 insertions(+), 214 deletions(-) delete mode 100644 src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.cpp create mode 100644 src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs delete mode 100644 src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.cpp create mode 100644 src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.rs diff --git a/src/common/cpp_essentials/base_extentions/qr_ec_level.rs b/src/common/cpp_essentials/base_extentions/qr_ec_level.rs index 365c7d3..ee582da 100644 --- a/src/common/cpp_essentials/base_extentions/qr_ec_level.rs +++ b/src/common/cpp_essentials/base_extentions/qr_ec_level.rs @@ -2,7 +2,7 @@ use crate::common::Result; use crate::qrcode::decoder::ErrorCorrectionLevel; impl ErrorCorrectionLevel { - pub fn ECLevelFromBits(bits: u8, isMicro: bool) -> Self { + pub fn ECLevelFromBitsSigned(bits: i8, isMicro: bool) -> Self { if (isMicro) { let LEVEL_FOR_BITS: [ErrorCorrectionLevel; 8] = [ ErrorCorrectionLevel::L, @@ -24,4 +24,8 @@ impl ErrorCorrectionLevel { ]; return LEVEL_FOR_BITS[bits as usize & 0x3]; } + + pub fn ECLevelFromBits(bits: u8, isMicro: bool) -> Self { + Self::ECLevelFromBitsSigned(bits as i8, isMicro) + } } diff --git a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs index 90b89de..b021306 100644 --- a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs +++ b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs @@ -125,4 +125,9 @@ impl FormatInformation { pub fn isValid(&self) -> bool { self.hammingDistance <= 3 } + + pub fn cpp_eq(&self, other: &Self) -> bool { + self.data_mask == other.data_mask + && self.error_correction_level == other.error_correction_level + } } diff --git a/src/common/cpp_essentials/decoder_result.rs b/src/common/cpp_essentials/decoder_result.rs index d8a9ab5..52862a0 100644 --- a/src/common/cpp_essentials/decoder_result.rs +++ b/src/common/cpp_essentials/decoder_result.rs @@ -200,3 +200,32 @@ where // } } + +impl DecoderResult +where + T: Copy + Clone + Default + Eq + PartialEq, +{ + pub fn text(&self) -> String { + self.content.to_string() + } + + pub fn symbologyIdentifier(&self) -> String { + let s = self.content.symbology; + if s.code > 0 { + format!( + "]{}{}", + vec!['1'; s.code as usize].into_iter().collect::(), + char::from( + s.modifier + + if self.content.is_eci { + s.eciModifierOffset + } else { + 0 + } + ) + ) + } else { + String::default() + } + } +} diff --git a/src/common/eci_string_builder.rs b/src/common/eci_string_builder.rs index 72bfa6a..b791d61 100644 --- a/src/common/eci_string_builder.rs +++ b/src/common/eci_string_builder.rs @@ -32,7 +32,7 @@ use super::{CharacterSet, Eci}; */ #[derive(Default, PartialEq, Eq, Debug, Clone)] pub struct ECIStringBuilder { - is_eci: bool, + pub is_eci: bool, eci_result: Option, bytes: Vec, eci_positions: Vec<(Eci, usize, usize)>, // (Eci, start, end) diff --git a/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.cpp b/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.cpp deleted file mode 100644 index 66d4f89..0000000 --- a/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* -* Copyright 2017 Huy Cuong Nguyen -* Copyright 2008 ZXing authors -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "BitArray.h" -#include "ByteArray.h" -#include "DecoderResult.h" -#include "qrcode/QRDataMask.h" -#include "qrcode/QRDecoder.h" -#include "qrcode/QRErrorCorrectionLevel.h" -#include "qrcode/QRVersion.h" - -#include "gtest/gtest.h" - -namespace ZXing { - namespace QRCode { - DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel ecLevel); - } -} - -using namespace ZXing; -using namespace ZXing::QRCode; - -TEST(QRDecodedBitStreamParserTest, SimpleByteMode) -{ - BitArray ba; - ba.appendBits(0x04, 4); // Byte mode - ba.appendBits(0x03, 8); // 3 bytes - ba.appendBits(0xF1, 8); - ba.appendBits(0xF2, 8); - ba.appendBits(0xF3, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); - EXPECT_EQ(L"\xF1\xF2\xF3", result); -} - -TEST(QRDecodedBitStreamParserTest, SimpleSJIS) -{ - BitArray ba; - ba.appendBits(0x04, 4); // Byte mode - ba.appendBits(0x04, 8); // 4 bytes - ba.appendBits(0xA1, 8); - ba.appendBits(0xA2, 8); - ba.appendBits(0xA3, 8); - ba.appendBits(0xD0, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); - EXPECT_EQ(L"\uff61\uff62\uff63\uff90", result); -} - -TEST(QRDecodedBitStreamParserTest, ECI) -{ - BitArray ba; - ba.appendBits(0x07, 4); // ECI mode - ba.appendBits(0x02, 8); // ECI 2 = CP437 encoding - ba.appendBits(0x04, 4); // Byte mode - ba.appendBits(0x03, 8); // 3 bytes - ba.appendBits(0xA1, 8); - ba.appendBits(0xA2, 8); - ba.appendBits(0xA3, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); - EXPECT_EQ(L"\xED\xF3\xFA", result); -} - -TEST(QRDecodedBitStreamParserTest, Hanzi) -{ - BitArray ba; - ba.appendBits(0x0D, 4); // Hanzi mode - ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding - ba.appendBits(0x01, 8); // 1 characters - ba.appendBits(0x03C1, 13); - auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); - EXPECT_EQ(L"\u963f", result); -} - -TEST(QRDecodedBitStreamParserTest, HanziLevel1) -{ - BitArray ba; - ba.appendBits(0x0D, 4); // Hanzi mode - ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding - ba.appendBits(0x01, 8); // 1 characters - // A5A2 (U+30A2) => A5A2 - A1A1 = 401, 4*60 + 01 = 0181 - ba.appendBits(0x0181, 13); - - auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); - EXPECT_EQ(L"\u30a2", result); -} - -TEST(QRDecodedBitStreamParserTest, SymbologyIdentifier) -{ - const Version& version = *Version::FromNumber(1); - const ErrorCorrectionLevel ecLevel = ErrorCorrectionLevel::Medium; - DecoderResult result; - - // Plain "ANUM(1) A" - result = DecodeBitStream({0x20, 0x09, 0x40}, version, ecLevel); - EXPECT_EQ(result.symbologyIdentifier(), "]Q1"); - EXPECT_EQ(result.text(), L"A"); - - // GS1 "FNC1(1st) NUM(4) 2001" - result = DecodeBitStream({0x51, 0x01, 0x0C, 0x81, 0x00}, version, ecLevel); - EXPECT_EQ(result.symbologyIdentifier(), "]Q3"); - EXPECT_EQ(result.text(), L"2001"); // "(20)01" - - // GS1 "NUM(4) 2001 FNC1(1st) 301" - FNC1(1st) can occur anywhere (this actually violates the specification) - result = DecodeBitStream({0x10, 0x10, 0xC8, 0x15, 0x10, 0x0D, 0x2D, 0x00}, version, ecLevel); - EXPECT_EQ(result.symbologyIdentifier(), "]Q3"); - EXPECT_EQ(result.text(), L"2001301"); // "(20)01(30)1" - - // AIM "FNC1(2nd) 99 (0x63) ANUM(1) A" - result = DecodeBitStream({0x96, 0x32, 0x00, 0x94, 0x00}, version, ecLevel); - EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); - EXPECT_EQ(result.text(), L"99A"); - - // AIM "BYTE(1) A FNC1(2nd) 99 (0x63) BYTE(1) B" - FNC1(2nd) can occur anywhere - // Disabled this test, since this violates the specification and the code does support it anymore -// result = DecodeBitStream({0x40, 0x14, 0x19, 0x63, 0x40, 0x14, 0x20, 0x00}, version, ecLevel, ""); -// EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); -// EXPECT_EQ(result.text(), L"99AB"); // Application Indicator prefixed to data - - // AIM "FNC1(2nd) A (100 + 61 = 0xA5) ANUM(1) B" - result = DecodeBitStream({0x9A, 0x52, 0x00, 0x96, 0x00}, version, ecLevel); - EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); - EXPECT_EQ(result.text(), L"AB"); - - // AIM "FNC1(2nd) a (100 + 97 = 0xC5) ANUM(1) B" - result = DecodeBitStream({0x9C, 0x52, 0x00, 0x96, 0x00}, version, ecLevel); - EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); - EXPECT_EQ(result.text(), L"aB"); - - // Bad AIM Application Indicator "FNC1(2nd) @ (0xA4) ANUM(1) B" - result = DecodeBitStream({0x9A, 0x42, 0x00, 0x96, 0x00}, version, ecLevel); - EXPECT_FALSE(result.isValid()); -} diff --git a/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs b/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs new file mode 100644 index 0000000..c00b897 --- /dev/null +++ b/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs @@ -0,0 +1,190 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +// #include "BitArray.h" +// #include "ByteArray.h" +// #include "DecoderResult.h" +// #include "qrcode/QRDataMask.h" +// #include "qrcode/QRDecoder.h" +// #include "qrcode/QRErrorCorrectionLevel.h" +// #include "qrcode/QRVersion.h" + +// #include "gtest/gtest.h" + +// namespace ZXing { +// namespace QRCode { +// DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel ecLevel); +// } +// } + +// using namespace ZXing; +// using namespace ZXing::QRCode; + +use crate::common::Result; + +use crate::{ + common::{cpp_essentials::DecoderResult, BitArray}, + qrcode::{ + cpp_port::decoder::DecodeBitStream, + decoder::{ErrorCorrectionLevel, Version}, + }, +}; + +#[test] +fn SimpleByteMode() { + let mut ba = BitArray::new(); + ba.appendBits(0x04, 4); // Byte mode + ba.appendBits(0x03, 8); // 3 bytes + ba.appendBits(0xF1, 8); + ba.appendBits(0xF2, 8); + ba.appendBits(0xF3, 8); + let bytes: Vec = ba.into(); + let result = DecodeBitStream( + &bytes, + Version::FromNumber(1, false).expect("find_version"), + ErrorCorrectionLevel::M, + ) + .expect("Decode") + .content() + .to_string(); + let str = String::from_utf16(&[0xF1, 0xF2, 0xF3]).unwrap(); + assert_eq!(str, result); +} + +#[test] +fn SimpleSJIS() { + let mut ba = BitArray::new(); + ba.appendBits(0x04, 4); // Byte mode + ba.appendBits(0x04, 8); // 4 bytes + ba.appendBits(0xA1, 8); + ba.appendBits(0xA2, 8); + ba.appendBits(0xA3, 8); + ba.appendBits(0xD0, 8); + let bytes: Vec = ba.into(); + let result = DecodeBitStream( + &bytes, + Version::FromNumber(1, false).unwrap(), + ErrorCorrectionLevel::M, + ) + .unwrap() + .content() + .to_string(); + assert_eq!("\u{ff61}\u{ff62}\u{ff63}\u{ff90}", result); +} + +#[test] +fn ECI() { + let mut ba = BitArray::new(); + ba.appendBits(0x07, 4); // ECI mode + ba.appendBits(0x02, 8); // ECI 2 = CP437 encoding + ba.appendBits(0x04, 4); // Byte mode + ba.appendBits(0x03, 8); // 3 bytes + ba.appendBits(0xA1, 8); + ba.appendBits(0xA2, 8); + ba.appendBits(0xA3, 8); + let bytes: Vec = ba.into(); + let result = DecodeBitStream( + &bytes, + Version::FromNumber(1, false).unwrap(), + ErrorCorrectionLevel::M, + ) + .unwrap() + .content() + .to_string(); + assert_eq!("\u{ED}\u{F3}\u{FA}", result); +} + +#[test] +fn Hanzi() { + let mut ba = BitArray::new(); + ba.appendBits(0x0D, 4); // Hanzi mode + ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding + ba.appendBits(0x01, 8); // 1 characters + ba.appendBits(0x03C1, 13); + let bytes: Vec = ba.into(); + let result = DecodeBitStream( + &bytes, + Version::FromNumber(1, false).unwrap(), + ErrorCorrectionLevel::M, + ) + .unwrap() + .content() + .to_string(); + assert_eq!("\u{963f}", result); +} + +#[test] +fn HanziLevel1() { + let mut ba = BitArray::new(); + ba.appendBits(0x0D, 4); // Hanzi mode + ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding + ba.appendBits(0x01, 8); // 1 characters + // A5A2 (U+30A2) => A5A2 - A1A1 = 401, 4*60 + 01 = 0181 + ba.appendBits(0x0181, 13); + + let bytes: Vec = ba.into(); + + let result = DecodeBitStream( + &bytes, + Version::FromNumber(1, false).unwrap(), + ErrorCorrectionLevel::M, + ) + .unwrap() + .content() + .to_string(); + assert_eq!("\u{30a2}", result); +} + +#[test] +fn SymbologyIdentifier() { + let version = Version::FromNumber(1, false).unwrap(); + let ecLevel = ErrorCorrectionLevel::M; + + // Plain "ANUM(1) A" + let result = DecodeBitStream(&[0x20, 0x09, 0x40], version, ecLevel).unwrap(); + assert_eq!(result.symbologyIdentifier(), "]Q1"); + assert_eq!(result.text(), "A"); + + // GS1 "FNC1(1st) NUM(4) 2001" + let result = DecodeBitStream(&[0x51, 0x01, 0x0C, 0x81, 0x00], version, ecLevel).unwrap(); + assert_eq!(result.symbologyIdentifier(), "]Q3"); + assert_eq!(result.text(), "2001"); // "(20)01" + + // GS1 "NUM(4) 2001 FNC1(1st) 301" - FNC1(1st) can occur anywhere (this actually violates the specification) + let result = DecodeBitStream( + &[0x10, 0x10, 0xC8, 0x15, 0x10, 0x0D, 0x2D, 0x00], + version, + ecLevel, + ) + .unwrap(); + assert_eq!(result.symbologyIdentifier(), "]Q3"); + assert_eq!(result.text(), "2001301"); // "(20)01(30)1" + + // AIM "FNC1(2nd) 99 (0x63) ANUM(1) A" + let result = DecodeBitStream(&[0x96, 0x32, 0x00, 0x94, 0x00], version, ecLevel).unwrap(); + assert_eq!(result.symbologyIdentifier(), "]Q5"); + assert_eq!(result.text(), "99A"); + + // AIM "BYTE(1) A FNC1(2nd) 99 (0x63) BYTE(1) B" - FNC1(2nd) can occur anywhere + // Disabled this test, since this violates the specification and the code does support it anymore + // result = DecodeBitStream({0x40, 0x14, 0x19, 0x63, 0x40, 0x14, 0x20, 0x00}, version, ecLevel, ""); + // assert_eq!(result.symbologyIdentifier(), "]Q5"); + // assert_eq!(result.text(), L"99AB"); // Application Indicator prefixed to data + + // AIM "FNC1(2nd) A (100 + 61 = 0xA5) ANUM(1) B" + let result = DecodeBitStream(&[0x9A, 0x52, 0x00, 0x96, 0x00], version, ecLevel).unwrap(); + assert_eq!(result.symbologyIdentifier(), "]Q5"); + assert_eq!(result.text(), "AB"); + + // AIM "FNC1(2nd) a (100 + 97 = 0xC5) ANUM(1) B" + let result = DecodeBitStream(&[0x9C, 0x52, 0x00, 0x96, 0x00], version, ecLevel).unwrap(); + assert_eq!(result.symbologyIdentifier(), "]Q5"); + assert_eq!(result.text(), "aB"); + + // Bad AIM Application Indicator "FNC1(2nd) @ (0xA4) ANUM(1) B" + let result = DecodeBitStream(&[0x9A, 0x42, 0x00, 0x96, 0x00], version, ecLevel).unwrap(); + assert!(!result.isValid()); +} diff --git a/src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.cpp b/src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.cpp deleted file mode 100644 index 77c1030..0000000 --- a/src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* -* Copyright 2017 Huy Cuong Nguyen -* Copyright 2008 ZXing authors -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "qrcode/QRErrorCorrectionLevel.h" - -#include "gtest/gtest.h" - -using namespace ZXing; -using namespace ZXing::QRCode; - -TEST(QRErrorCorrectionLevelTest, ForBits) -{ - EXPECT_EQ(ErrorCorrectionLevel::Medium, ECLevelFromBits(0)); - EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(1)); - EXPECT_EQ(ErrorCorrectionLevel::High, ECLevelFromBits(2)); - EXPECT_EQ(ErrorCorrectionLevel::Quality, ECLevelFromBits(3)); -} - -TEST(QRErrorCorrectionLevelTest, ForMicroBits) -{ - EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(0, true)); - EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(1, true)); - EXPECT_EQ(ErrorCorrectionLevel::Medium, ECLevelFromBits(2, true)); - EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(3, true)); - EXPECT_EQ(ErrorCorrectionLevel::Medium, ECLevelFromBits(4, true)); - EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(5, true)); - EXPECT_EQ(ErrorCorrectionLevel::Medium, ECLevelFromBits(6, true)); - EXPECT_EQ(ErrorCorrectionLevel::Quality, ECLevelFromBits(7, true)); - - EXPECT_EQ(ErrorCorrectionLevel::Quality, ECLevelFromBits(-1, true)); - EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(8, true)); -} - -TEST(QRErrorCorrectionLevelTest, ToString) -{ - using namespace std::literals; - - EXPECT_EQ("L"s, ToString(ErrorCorrectionLevel::Low)); - EXPECT_EQ("M"s, ToString(ErrorCorrectionLevel::Medium)); - EXPECT_EQ("Q"s, ToString(ErrorCorrectionLevel::Quality)); - EXPECT_EQ("H"s, ToString(ErrorCorrectionLevel::High)); -} diff --git a/src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.rs b/src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.rs new file mode 100644 index 0000000..a8eb509 --- /dev/null +++ b/src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.rs @@ -0,0 +1,89 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +// #include "qrcode/QRErrorCorrectionLevel.h" + +// #include "gtest/gtest.h" + +// using namespace ZXing; +// using namespace ZXing::QRCode; + +use crate::qrcode::decoder::ErrorCorrectionLevel; + +#[test] +fn ForBits() { + assert_eq!( + ErrorCorrectionLevel::M, + ErrorCorrectionLevel::ECLevelFromBits(0, false) + ); + assert_eq!( + ErrorCorrectionLevel::L, + ErrorCorrectionLevel::ECLevelFromBits(1, false) + ); + assert_eq!( + ErrorCorrectionLevel::H, + ErrorCorrectionLevel::ECLevelFromBits(2, false) + ); + assert_eq!( + ErrorCorrectionLevel::Q, + ErrorCorrectionLevel::ECLevelFromBits(3, false) + ); +} + +#[test] +fn ForMicroBits() { + assert_eq!( + ErrorCorrectionLevel::L, + ErrorCorrectionLevel::ECLevelFromBits(0, true) + ); + assert_eq!( + ErrorCorrectionLevel::L, + ErrorCorrectionLevel::ECLevelFromBits(1, true) + ); + assert_eq!( + ErrorCorrectionLevel::M, + ErrorCorrectionLevel::ECLevelFromBits(2, true) + ); + assert_eq!( + ErrorCorrectionLevel::L, + ErrorCorrectionLevel::ECLevelFromBits(3, true) + ); + assert_eq!( + ErrorCorrectionLevel::M, + ErrorCorrectionLevel::ECLevelFromBits(4, true) + ); + assert_eq!( + ErrorCorrectionLevel::L, + ErrorCorrectionLevel::ECLevelFromBits(5, true) + ); + assert_eq!( + ErrorCorrectionLevel::M, + ErrorCorrectionLevel::ECLevelFromBits(6, true) + ); + assert_eq!( + ErrorCorrectionLevel::Q, + ErrorCorrectionLevel::ECLevelFromBits(7, true) + ); + + assert_eq!( + ErrorCorrectionLevel::Q, + ErrorCorrectionLevel::ECLevelFromBitsSigned(-1, true) + ); + assert_eq!( + ErrorCorrectionLevel::L, + ErrorCorrectionLevel::ECLevelFromBits(8, true) + ); +} + +#[test] +fn ToString() { + // using namespace std::literals; + + assert_eq!("L", ErrorCorrectionLevel::L.to_string()); + assert_eq!("M", ErrorCorrectionLevel::M.to_string()); + assert_eq!("Q", ErrorCorrectionLevel::Q.to_string()); + assert_eq!("H", ErrorCorrectionLevel::H.to_string()); +} diff --git a/src/qrcode/cpp_port/test/QRFormatInformationTest.rs b/src/qrcode/cpp_port/test/QRFormatInformationTest.rs index 0d2a867..67f326a 100644 --- a/src/qrcode/cpp_port/test/QRFormatInformationTest.rs +++ b/src/qrcode/cpp_port/test/QRFormatInformationTest.rs @@ -28,36 +28,40 @@ fn Decode() { assert_eq!(0x07, expected.data_mask); assert_eq!(ErrorCorrectionLevel::Q, expected.error_correction_level); // where the code forgot the mask! - assert_eq!( - expected, - FormatInformation::DecodeQR(UNMASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2) - ); + // assert_eq!( + // expected, + // FormatInformation::DecodeQR(UNMASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2) + // ); + cpp_eq( + &expected, + &FormatInformation::DecodeQR(UNMASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2), + ) } #[test] fn DecodeWithBitDifference() { let expected = FormatInformation::DecodeQR(MASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2); // 1,2,3,4 bits difference - assert_eq!( - expected, - FormatInformation::DecodeQR( + cpp_eq( + &expected, + &FormatInformation::DecodeQR( MASKED_TEST_FORMAT_INFO ^ 0x01, - MASKED_TEST_FORMAT_INFO2 ^ 0x01 - ) + MASKED_TEST_FORMAT_INFO2 ^ 0x01, + ), ); - assert_eq!( - expected, - FormatInformation::DecodeQR( + cpp_eq( + &expected, + &FormatInformation::DecodeQR( MASKED_TEST_FORMAT_INFO ^ 0x03, - MASKED_TEST_FORMAT_INFO2 ^ 0x03 - ) + MASKED_TEST_FORMAT_INFO2 ^ 0x03, + ), ); - assert_eq!( - expected, - FormatInformation::DecodeQR( + cpp_eq( + &expected, + &FormatInformation::DecodeQR( MASKED_TEST_FORMAT_INFO ^ 0x07, - MASKED_TEST_FORMAT_INFO2 ^ 0x07 - ) + MASKED_TEST_FORMAT_INFO2 ^ 0x07, + ), ); assert!(!FormatInformation::DecodeQR( MASKED_TEST_FORMAT_INFO ^ 0x0F, @@ -69,12 +73,12 @@ fn DecodeWithBitDifference() { #[test] fn DecodeWithMisread() { let expected = FormatInformation::DecodeQR(MASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2); - assert_eq!( - expected, - FormatInformation::DecodeQR( + cpp_eq( + &expected, + &FormatInformation::DecodeQR( MASKED_TEST_FORMAT_INFO ^ 0x03, - MASKED_TEST_FORMAT_INFO2 ^ 0x0F - ) + MASKED_TEST_FORMAT_INFO2 ^ 0x0F, + ), ); } @@ -102,17 +106,17 @@ fn DecodeMicroWithBitDifference() { let expected = FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO); // 1,2,3 bits difference - assert_eq!( - expected, - FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x01) + cpp_eq( + &expected, + &FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x01), ); - assert_eq!( - expected, - FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x03) + cpp_eq( + &expected, + &FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x03), ); - assert_eq!( - expected, - FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x07) + cpp_eq( + &expected, + &FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x07), ); // Bigger bit differences can return valid FormatInformation objects but the data mask and error @@ -122,3 +126,7 @@ fn DecodeMicroWithBitDifference() { // EXPECT_NE(expected.errorCorrectionLevel(), // FormatInformation::DecodeFormatInformation(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x3f).errorCorrectionLevel()); } + +fn cpp_eq(rhs: &FormatInformation, lhs: &FormatInformation) { + assert!(rhs.cpp_eq(lhs)) +} diff --git a/src/qrcode/cpp_port/test/mod.rs b/src/qrcode/cpp_port/test/mod.rs index 4aaae93..9383384 100644 --- a/src/qrcode/cpp_port/test/mod.rs +++ b/src/qrcode/cpp_port/test/mod.rs @@ -2,3 +2,7 @@ mod QRModeTest; mod QRVersionTest; mod QRFormatInformationTest; + +mod QRErrorCorrectionLevelTest; + +mod QRDecodedBitStreamParserTest; From 23951ac87c390135d5001df70781620c4fc3b825 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 14 Apr 2023 11:03:04 -0500 Subject: [PATCH 47/72] ported test SimpleSJIS and fixed regression pdf417 --- src/common/bit_array.rs | 17 +++--- src/common/cpp_essentials/decoder_result.rs | 2 +- src/common/eci.rs | 17 ++++++ src/common/eci_string_builder.rs | 56 +++++++++++++++---- src/qrcode/cpp_port/decoder.rs | 16 +++--- .../test/QRDecodedBitStreamParserTest.rs | 54 +++++++++--------- 6 files changed, 109 insertions(+), 53 deletions(-) diff --git a/src/common/bit_array.rs b/src/common/bit_array.rs index 26c1851..4155c37 100644 --- a/src/common/bit_array.rs +++ b/src/common/bit_array.rs @@ -405,12 +405,15 @@ impl Default for BitArray { impl Into> for BitArray { fn into(self) -> Vec { - let mut arr = vec![0; self.get_size()]; - for x in 0..self.get_size() { - if self.get(x) { - arr[x] = 1; - } - } - arr + let mut array = vec![0; self.getSizeInBytes()]; + self.toBytes(0, &mut array, 0, self.getSizeInBytes()); + array + // let mut arr = vec![0; self.get_size()]; + // for x in 0..self.get_size() { + // if self.get(x) { + // arr[x] = 1; + // } + // } + // arr } } diff --git a/src/common/cpp_essentials/decoder_result.rs b/src/common/cpp_essentials/decoder_result.rs index 52862a0..c1d25d3 100644 --- a/src/common/cpp_essentials/decoder_result.rs +++ b/src/common/cpp_essentials/decoder_result.rs @@ -217,7 +217,7 @@ where vec!['1'; s.code as usize].into_iter().collect::(), char::from( s.modifier - + if self.content.is_eci { + + if self.content.has_eci { s.eciModifierOffset } else { 0 diff --git a/src/common/eci.rs b/src/common/eci.rs index da47109..3d8e6ce 100644 --- a/src/common/eci.rs +++ b/src/common/eci.rs @@ -1,7 +1,11 @@ use std::fmt::Display; +use crate::Exceptions; + use super::CharacterSet; +use crate::common::Result; + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Eci { Unknown = -1, @@ -44,6 +48,19 @@ impl Eci { pub fn can_encode(self) -> bool { (self as i32) >= 899 } + + pub fn try_from_i32(value: i32) -> Result { + let v = Self::from(value); + if v == Eci::Unknown { + Err(Exceptions::ILLEGAL_ARGUMENT) + } else { + Ok(v) + } + } + + pub fn try_from_u32(value: u32) -> Result { + Self::try_from_i32(value as i32) + } } impl From for Eci { diff --git a/src/common/eci_string_builder.rs b/src/common/eci_string_builder.rs index b791d61..2aee154 100644 --- a/src/common/eci_string_builder.rs +++ b/src/common/eci_string_builder.rs @@ -21,9 +21,9 @@ // import java.nio.charset.Charset; // import java.nio.charset.StandardCharsets; -use std::fmt; +use std::{collections::HashMap, fmt}; -use super::{CharacterSet, Eci}; +use super::{CharacterSet, Eci, StringUtils}; /** * Class that converts a sequence of ECIs and bytes into a string @@ -32,7 +32,7 @@ use super::{CharacterSet, Eci}; */ #[derive(Default, PartialEq, Eq, Debug, Clone)] pub struct ECIStringBuilder { - pub is_eci: bool, + pub has_eci: bool, eci_result: Option, bytes: Vec, eci_positions: Vec<(Eci, usize, usize)>, // (Eci, start, end) @@ -45,7 +45,7 @@ impl ECIStringBuilder { eci_result: None, bytes: Vec::with_capacity(initial_capacity), eci_positions: Vec::default(), - is_eci: false, + has_eci: false, symbology: SymbologyIdentifier::default(), } } @@ -109,11 +109,12 @@ impl ECIStringBuilder { */ pub fn append_eci(&mut self, eci: Eci) { self.eci_result = None; - if !self.is_eci && eci != Eci::ISO8859_1 { - self.is_eci = true; + + if !self.has_eci && eci != Eci::ISO8859_1 { + self.has_eci = true; } - if self.is_eci { + if self.has_eci { if let Some(last) = self.eci_positions.last_mut() { last.2 = self.bytes.len() } @@ -124,7 +125,17 @@ impl ECIStringBuilder { /// Change the current encoding characterset, finding an eci to do so pub fn switch_encoding(&mut self, charset: CharacterSet) { - self.append_eci(Eci::from(charset)) + //self.append_eci(Eci::from(charset)) + if (false && !self.has_eci) { + self.eci_positions.clear(); + } + if (false || !self.has_eci) + //{self.eci_positions.push_back({eci, Size(bytes)});} + { + self.append_eci(Eci::from(charset)) + } + + self.has_eci |= false; } /// Finishes encoding anything in the buffer using the current ECI and resets. @@ -133,7 +144,7 @@ impl ECIStringBuilder { pub fn encodeCurrentBytesIfAny(&self) -> String { let mut encoded_string = String::with_capacity(self.bytes.len()); // First encode the first set - let (_, end, _) = + let (_eci, end, _) = *self .eci_positions .first() @@ -143,6 +154,10 @@ impl ECIStringBuilder { &Self::encode_segment(&self.bytes[0..end], Eci::ISO8859_1).unwrap_or_default(), ); + if end == self.bytes.len() { + return encoded_string; + } + // If there are more sets, encode each of them in turn for (eci, eci_start, eci_end) in &self.eci_positions { // let (_,end) = *self.eci_positions.first().unwrap_or(&(*eci, self.bytes.len())); @@ -161,20 +176,41 @@ impl ECIStringBuilder { } fn encode_segment(bytes: &[u8], eci: Eci) -> Option { + let mut not_encoded_yet = true; let mut encoded_string = String::with_capacity(bytes.len()); if ![Eci::Binary, Eci::Unknown].contains(&eci) { if eci == Eci::UTF8 { if !bytes.is_empty() { encoded_string.push_str(&CharacterSet::UTF8.decode(bytes).ok()?); + not_encoded_yet = false; } else { return None; } } else if !bytes.is_empty() { encoded_string.push_str(&CharacterSet::from(eci).decode(bytes).ok()?); + not_encoded_yet = false; } else { return None; } - } else { + } else if eci == Eci::Unknown { + /* // This probably should never be used, it's here just in case I don't understand what's + // going on. + let cs = CharacterSet::from(Eci::ISO8859_1); + if let Ok(enc_str) = cs.decode(bytes) { + encoded_string.push_str(&enc_str); + not_encoded_yet = false; + } + + else */ + if let Some(found_encoding) = StringUtils::guessCharset(bytes, &HashMap::default()) { + if let Ok(found_encoded_str) = found_encoding.decode(bytes) { + encoded_string.push_str(&found_encoded_str); + not_encoded_yet = false; + } + } + } + + if not_encoded_yet { for byte in bytes { encoded_string.push(char::from(*byte)) } diff --git a/src/qrcode/cpp_port/decoder.rs b/src/qrcode/cpp_port/decoder.rs index bda6371..279b4cf 100644 --- a/src/qrcode/cpp_port/decoder.rs +++ b/src/qrcode/cpp_port/decoder.rs @@ -125,7 +125,7 @@ pub fn DecodeByteSegment( result.switch_encoding(CharacterSet::Unknown); result.reserve(count as usize); - for i in 0..count { + for _i in 0..count { // for (int i = 0; i < count; i++) *result += (bits.readBits(8)?) as u8; } @@ -297,7 +297,7 @@ pub fn DecodeBitStream( let mut structuredAppend = StructuredAppendInfo::default(); let modeBitLength = Mode::get_codec_mode_bits_length(version); - let res = (|| { + let _res = (|| { while (!IsEndOfStream(&mut bits, version)?) { let mode: Mode; if (modeBitLength == 0) { @@ -361,26 +361,26 @@ pub fn DecodeBitStream( // throw FormatError("Unsupported HANZI subset"); } let count = bits.readBits(mode.CharacterCountBits(version) as usize)?; - DecodeHanziSegment(&mut bits, count, &mut result); + DecodeHanziSegment(&mut bits, count, &mut result)?; } _ => { // "Normal" QR code modes: // How many characters will follow, encoded in this mode? let count = bits.readBits(mode.CharacterCountBits(version) as usize)?; match (mode) { - Mode::NUMERIC => DecodeNumericSegment(&mut bits, count, &mut result), + Mode::NUMERIC => DecodeNumericSegment(&mut bits, count, &mut result)?, Mode::ALPHANUMERIC => { - DecodeAlphanumericSegment(&mut bits, count, &mut result) + DecodeAlphanumericSegment(&mut bits, count, &mut result)? } - Mode::BYTE => DecodeByteSegment(&mut bits, count, &mut result), - Mode::KANJI => DecodeKanjiSegment(&mut bits, count, &mut result), + Mode::BYTE => DecodeByteSegment(&mut bits, count, &mut result)?, + Mode::KANJI => DecodeKanjiSegment(&mut bits, count, &mut result)?, _ => return Err(Exceptions::format_with("Invalid CodecMode")), //throw FormatError("Invalid CodecMode"); }; } } } Ok(()) - })(); + })()?; // } catch (std::out_of_range& e) { // see BitSource::readBits // error = FormatError("Truncated bit stream"); // } catch (Error e) { diff --git a/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs b/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs index c00b897..9cfb93f 100644 --- a/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs +++ b/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs @@ -36,11 +36,11 @@ use crate::{ #[test] fn SimpleByteMode() { let mut ba = BitArray::new(); - ba.appendBits(0x04, 4); // Byte mode - ba.appendBits(0x03, 8); // 3 bytes - ba.appendBits(0xF1, 8); - ba.appendBits(0xF2, 8); - ba.appendBits(0xF3, 8); + ba.appendBits(0x04, 4).unwrap(); // Byte mode + ba.appendBits(0x03, 8).unwrap(); // 3 bytes + ba.appendBits(0xF1, 8).unwrap(); + ba.appendBits(0xF2, 8).unwrap(); + ba.appendBits(0xF3, 8).unwrap(); let bytes: Vec = ba.into(); let result = DecodeBitStream( &bytes, @@ -57,12 +57,12 @@ fn SimpleByteMode() { #[test] fn SimpleSJIS() { let mut ba = BitArray::new(); - ba.appendBits(0x04, 4); // Byte mode - ba.appendBits(0x04, 8); // 4 bytes - ba.appendBits(0xA1, 8); - ba.appendBits(0xA2, 8); - ba.appendBits(0xA3, 8); - ba.appendBits(0xD0, 8); + ba.appendBits(0x04, 4).expect("append"); // Byte mode + ba.appendBits(0x04, 8).expect("append"); // 4 bytes + ba.appendBits(0xA1, 8).expect("append"); + ba.appendBits(0xA2, 8).expect("append"); + ba.appendBits(0xA3, 8).expect("append"); + ba.appendBits(0xD0, 8).expect("append"); let bytes: Vec = ba.into(); let result = DecodeBitStream( &bytes, @@ -78,13 +78,13 @@ fn SimpleSJIS() { #[test] fn ECI() { let mut ba = BitArray::new(); - ba.appendBits(0x07, 4); // ECI mode - ba.appendBits(0x02, 8); // ECI 2 = CP437 encoding - ba.appendBits(0x04, 4); // Byte mode - ba.appendBits(0x03, 8); // 3 bytes - ba.appendBits(0xA1, 8); - ba.appendBits(0xA2, 8); - ba.appendBits(0xA3, 8); + ba.appendBits(0x07, 4).expect("append"); // ECI mode + ba.appendBits(0x02, 8).expect("append"); // ECI 2 = CP437 encoding + ba.appendBits(0x04, 4).expect("append"); // Byte mode + ba.appendBits(0x03, 8).expect("append"); // 3 bytes + ba.appendBits(0xA1, 8).expect("append"); + ba.appendBits(0xA2, 8).expect("append"); + ba.appendBits(0xA3, 8).expect("append"); let bytes: Vec = ba.into(); let result = DecodeBitStream( &bytes, @@ -100,10 +100,10 @@ fn ECI() { #[test] fn Hanzi() { let mut ba = BitArray::new(); - ba.appendBits(0x0D, 4); // Hanzi mode - ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding - ba.appendBits(0x01, 8); // 1 characters - ba.appendBits(0x03C1, 13); + ba.appendBits(0x0D, 4).expect("append"); // Hanzi mode + ba.appendBits(0x01, 4).expect("append"); // Subset 1 = GB2312 encoding + ba.appendBits(0x01, 8).expect("append"); // 1 characters + ba.appendBits(0x03C1, 13).expect("append"); let bytes: Vec = ba.into(); let result = DecodeBitStream( &bytes, @@ -119,11 +119,11 @@ fn Hanzi() { #[test] fn HanziLevel1() { let mut ba = BitArray::new(); - ba.appendBits(0x0D, 4); // Hanzi mode - ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding - ba.appendBits(0x01, 8); // 1 characters - // A5A2 (U+30A2) => A5A2 - A1A1 = 401, 4*60 + 01 = 0181 - ba.appendBits(0x0181, 13); + ba.appendBits(0x0D, 4).expect("append"); // Hanzi mode + ba.appendBits(0x01, 4).expect("append"); // Subset 1 = GB2312 encoding + ba.appendBits(0x01, 8).expect("append"); // 1 characters + // A5A2 (U+30A2) => A5A2 - A1A1 = 401, 4*60 + 01 = 0181 + ba.appendBits(0x0181, 13).expect("append"); let bytes: Vec = ba.into(); From de01f5c7494a955129428aa21497fad79586afcb Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 14 Apr 2023 11:42:47 -0500 Subject: [PATCH 48/72] port SymbologyIdentifier test --- src/common/cpp_essentials/decoder_result.rs | 19 ++++++++++++++++--- src/common/cpp_essentials/util.rs | 16 +++++++++------- src/exceptions.rs | 2 +- src/qrcode/cpp_port/decoder.rs | 16 +++------------- src/qrcode/cpp_port/detector.rs | 4 +++- 5 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/common/cpp_essentials/decoder_result.rs b/src/common/cpp_essentials/decoder_result.rs index c1d25d3..a2e85fb 100644 --- a/src/common/cpp_essentials/decoder_result.rs +++ b/src/common/cpp_essentials/decoder_result.rs @@ -1,6 +1,6 @@ use std::{any::Any, rc::Rc}; -use crate::common::ECIStringBuilder; +use crate::{common::ECIStringBuilder, Exceptions}; use super::StructuredAppendInfo; @@ -18,6 +18,7 @@ where readerInit: bool, // = false; //Error _error; //std::shared_ptr _extra; + error: Option, extra: Rc, } @@ -34,6 +35,7 @@ where structuredAppend: Default::default(), isMirrored: false, readerInit: false, + error: None, extra: Default::default(), } } @@ -53,7 +55,7 @@ where } pub fn isValid(&self) -> bool { - self.content.symbology.code != 0 + self.content.symbology.code != 0 && self.error.is_none() //return includeErrors || (_content.symbology.code != 0 && !_error); } @@ -196,6 +198,17 @@ where self } + pub fn error(&self) -> &Option { + &self.error + } + pub fn setError(&mut self, error: Option) { + self.error = error + } + pub fn withError(mut self, error: Option) -> DecoderResult { + self.setError(error); + self + } + // pub fn build(self) -> DecoderResult { // } @@ -214,7 +227,7 @@ where if s.code > 0 { format!( "]{}{}", - vec!['1'; s.code as usize].into_iter().collect::(), + char::from(s.code), char::from( s.modifier + if self.content.has_eci { diff --git a/src/common/cpp_essentials/util.rs b/src/common/cpp_essentials/util.rs index 3947164..97ddde1 100644 --- a/src/common/cpp_essentials/util.rs +++ b/src/common/cpp_essentials/util.rs @@ -41,26 +41,28 @@ pub fn UpdateMinMaxFloat(min: &mut f64, max: &mut f64, val: f64) { // template>> pub fn ToString>(val: T, len: usize) -> Result { - let mut len = len; - let mut val = val.into(); + let mut len = len as isize; + let val = val.into(); + let mut val = val as isize; - let mut result: String = vec!['0'; len].iter().collect(); + let mut result = vec!['0'; len as usize]; len -= 1; // std::string result(len--, '0'); - if (val < 0) { + if val < 0 { return Err(Exceptions::format_with("Invalid value")); } while len >= 0 && val != 0 { - result.replace_range(len..len, &char::from(b'0' + (val % 10) as u8).to_string()); + result[len as usize] = char::from(b'0' + (val % 10) as u8); + // result.replace_range((len as usize)..(len as usize), &char::from(b'0' + (val % 10) as u8).to_string()); len -= 1; val /= 10; } // for (; len >= 0 && val != 0; --len, val /= 10) { // result[len] = '0' + val % 10;} - if (val != 0) { + if val != 0 { return Err(Exceptions::format_with("Invalid value")); } - Ok(result) + Ok(result.iter().collect()) } diff --git a/src/exceptions.rs b/src/exceptions.rs index 6001fa5..ae2788d 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Error, Debug, PartialEq, Eq)] +#[derive(Error, Debug, PartialEq, Eq, Clone)] pub enum Exceptions { #[error("IllegalArgumentException{}", if .0.is_empty() { String::new() } else { format!(" - {}", .0) })] IllegalArgumentException(String), diff --git a/src/qrcode/cpp_port/decoder.rs b/src/qrcode/cpp_port/decoder.rs index 279b4cf..0dd0e4d 100644 --- a/src/qrcode/cpp_port/decoder.rs +++ b/src/qrcode/cpp_port/decoder.rs @@ -297,7 +297,7 @@ pub fn DecodeBitStream( let mut structuredAppend = StructuredAppendInfo::default(); let modeBitLength = Mode::get_codec_mode_bits_length(version); - let _res = (|| { + let res = (|| { while (!IsEndOfStream(&mut bits, version)?) { let mode: Mode; if (modeBitLength == 0) { @@ -380,23 +380,13 @@ pub fn DecodeBitStream( } } Ok(()) - })()?; - // } catch (std::out_of_range& e) { // see BitSource::readBits - // error = FormatError("Truncated bit stream"); - // } catch (Error e) { - // error = std::move(e); - // } + })(); Ok(DecoderResult::with_eci_string_builder(result) + .withError(res.err()) .withEcLevel(ecLevel.to_string()) .withVersionNumber(version.getVersionNumber()) .withStructuredAppend(structuredAppend)) - - // return DecoderResult(std::move(result)) - // .setError(std::move(error)) - // .setEcLevel(ToString(ecLevel)) - // .setVersionNumber(version.versionNumber()) - // .setStructuredAppend(structuredAppend); } pub fn Decode(bits: &BitMatrix) -> Result> { diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index f0e159a..718791b 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -308,6 +308,7 @@ pub fn EstimateDimension( } } +/// This function can panic pub fn TraceLine(image: &BitMatrix, p: Point, d: Point, edge: i32) -> impl RegressionLineTrait { let mut cur = EdgeTracer::new(image, p, d - p); let mut line = RegressionLine::default(); @@ -337,7 +338,8 @@ pub fn TraceLine(image: &BitMatrix, p: Point, d: Point, edge: i32) -> impl Regre let mut c = EdgeTracer::new(image, curI.p, curI.direction(dir)); let stepCount = (Point::maxAbsComponent(cur.p - p)) as i32; loop { - line.add(Point::centered(c.p)); + line.add(Point::centered(c.p)) + .expect("could not add point on line"); if !(--stepCount > 0 && c.stepAlongEdge(dir, Some(true))) { break; From 8462c7e877193e7779b4490c08278b2508e45e3f Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 14 Apr 2023 11:55:56 -0500 Subject: [PATCH 49/72] port QRDataMaskTest --- src/qrcode/cpp_port/test/QRDataMaskTest.cpp | 100 --------------- src/qrcode/cpp_port/test/QRDataMaskTest.rs | 135 ++++++++++++++++++++ src/qrcode/cpp_port/test/mod.rs | 2 + 3 files changed, 137 insertions(+), 100 deletions(-) delete mode 100644 src/qrcode/cpp_port/test/QRDataMaskTest.cpp create mode 100644 src/qrcode/cpp_port/test/QRDataMaskTest.rs diff --git a/src/qrcode/cpp_port/test/QRDataMaskTest.cpp b/src/qrcode/cpp_port/test/QRDataMaskTest.cpp deleted file mode 100644 index 24baa4a..0000000 --- a/src/qrcode/cpp_port/test/QRDataMaskTest.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* -* Copyright 2017 Huy Cuong Nguyen -* Copyright 2007 ZXing authors -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "qrcode/QRDataMask.h" -#include "BitMatrix.h" - -#include "gtest/gtest.h" -#include - -using namespace ZXing; -using namespace ZXing::QRCode; - -namespace { - - void TestMaskAcrossDimensionsImpl(int maskIndex, bool isMicro, const int versionMax, const int dimensionStart, const int dimensionStep, std::function condition) - { - for (int version = 1; version <= versionMax; version++) { - int dimension = dimensionStart + dimensionStep * version; - BitMatrix bits(dimension); - - for (int i = 0; i < dimension; i++) - for (int j = 0; j < dimension; j++) - EXPECT_EQ(GetMaskedBit(bits, j, i, maskIndex, isMicro), condition(i, j)) << "(" << i << ',' << j << ')'; - } - } - - void TestMaskAcrossDimensions(int maskIndex, std::function condition) - { - TestMaskAcrossDimensionsImpl(maskIndex, false, 40, 17, 4, condition); - } - - void TestMicroMaskAcrossDimensions(int maskIndex, std::function condition) - { - TestMaskAcrossDimensionsImpl(maskIndex, true, 4, 9, 2, condition); - } - -} - -TEST(QRDataMaskTest, Mask0) -{ - TestMaskAcrossDimensions(0, [](int i, int j) { return (i + j) % 2 == 0; }); -} - -TEST(QRDataMaskTest, Mask1) -{ - TestMaskAcrossDimensions(1, [](int i, int) { return i % 2 == 0; }); -} - -TEST(QRDataMaskTest, Mask2) -{ - TestMaskAcrossDimensions(2, [](int, int j) { return j % 3 == 0; }); -} - -TEST(QRDataMaskTest, Mask3) -{ - TestMaskAcrossDimensions(3, [](int i, int j) { return (i + j) % 3 == 0; }); -} - -TEST(QRDataMaskTest, Mask4) -{ - TestMaskAcrossDimensions(4, [](int i, int j) { return (i / 2 + j / 3) % 2 == 0; }); -} - -TEST(QRDataMaskTest, Mask5) -{ - TestMaskAcrossDimensions(5, [](int i, int j) { return (i * j) % 2 + (i * j) % 3 == 0; }); -} - -TEST(QRDataMaskTest, Mask6) -{ - TestMaskAcrossDimensions(6, [](int i, int j) { return ((i * j) % 2 + (i * j) % 3) % 2 == 0; }); -} - -TEST(QRDataMaskTest, Mask7) -{ - TestMaskAcrossDimensions(7, [](int i, int j) { return ((i + j) % 2 + (i * j) % 3) % 2 == 0; }); -} - -TEST(QRDataMaskTest, MicroMask0) -{ - TestMicroMaskAcrossDimensions(0, [](int i, int) { return i % 2 == 0; }); -} - -TEST(QRDataMaskTest, MicroMask1) -{ - TestMicroMaskAcrossDimensions(1, [](int i, int j) { return (i / 2 + j / 3) % 2 == 0; }); -} - -TEST(QRDataMaskTest, MicroMask2) -{ - TestMicroMaskAcrossDimensions(2, [](int i, int j) { return ((i * j) % 2 + (i * j) % 3) % 2 == 0; }); -} - -TEST(QRDataMaskTest, MicroMask3) -{ - TestMicroMaskAcrossDimensions(3, [](int i, int j) { return ((i + j) % 2 + (i * j) % 3) % 2 == 0; }); -} diff --git a/src/qrcode/cpp_port/test/QRDataMaskTest.rs b/src/qrcode/cpp_port/test/QRDataMaskTest.rs new file mode 100644 index 0000000..12e7d0b --- /dev/null +++ b/src/qrcode/cpp_port/test/QRDataMaskTest.rs @@ -0,0 +1,135 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2007 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +use crate::{common::BitMatrix, qrcode::cpp_port::data_mask::GetMaskedBit}; + +#[test] +fn Mask0() { + TestMaskAcrossDimensions(0, |i, j| { + return (i + j) % 2 == 0; + }); +} + +#[test] +fn Mask1() { + TestMaskAcrossDimensions(1, |i, _| { + return i % 2 == 0; + }); +} + +#[test] +fn Mask2() { + TestMaskAcrossDimensions(2, |_, j| { + return j % 3 == 0; + }); +} + +#[test] +fn Mask3() { + TestMaskAcrossDimensions(3, |i, j| { + return (i + j) % 3 == 0; + }); +} + +#[test] +fn Mask4() { + TestMaskAcrossDimensions(4, |i, j| { + return (i / 2 + j / 3) % 2 == 0; + }); +} + +#[test] +fn Mask5() { + TestMaskAcrossDimensions(5, |i, j| { + return (i * j) % 2 + (i * j) % 3 == 0; + }); +} + +#[test] +fn Mask6() { + TestMaskAcrossDimensions(6, |i, j| { + return ((i * j) % 2 + (i * j) % 3) % 2 == 0; + }); +} + +#[test] +fn Mask7() { + TestMaskAcrossDimensions(7, |i, j| { + return ((i + j) % 2 + (i * j) % 3) % 2 == 0; + }); +} + +#[test] +fn MicroMask0() { + TestMicroMaskAcrossDimensions(0, |i, _| { + return i % 2 == 0; + }); +} + +#[test] +fn MicroMask1() { + TestMicroMaskAcrossDimensions(1, |i, j| { + return (i / 2 + j / 3) % 2 == 0; + }); +} + +#[test] +fn MicroMask2() { + TestMicroMaskAcrossDimensions(2, |i, j| { + return ((i * j) % 2 + (i * j) % 3) % 2 == 0; + }); +} + +#[test] +fn MicroMask3() { + TestMicroMaskAcrossDimensions(3, |i, j| { + return ((i + j) % 2 + (i * j) % 3) % 2 == 0; + }); +} + +fn TestMaskAcrossDimensionsImpl( + maskIndex: u32, + isMicro: bool, + versionMax: u32, + dimensionStart: u32, + dimensionStep: u32, + condition: F, +) where + F: Fn(u32, u32) -> bool, +{ + for version in 1..=versionMax { + // for (int version = 1; version <= versionMax; version++) { + let dimension = dimensionStart + dimensionStep * version; + let bits = BitMatrix::with_single_dimension(dimension).expect("couldn't create bitmatrix"); + + for i in 0..dimension { + // for (int i = 0; i < dimension; i++) + for j in 0..dimension { + // for (int j = 0; j < dimension; j++) + assert_eq!( + GetMaskedBit(&bits, j, i, maskIndex, Some(isMicro)) + .expect("could not get mask bit"), + condition(i, j), + "({i},{j})" + ); + } + } + } +} + +fn TestMaskAcrossDimensions(maskIndex: u32, condition: F) +where + F: Fn(u32, u32) -> bool, +{ + TestMaskAcrossDimensionsImpl(maskIndex, false, 40, 17, 4, condition); +} + +fn TestMicroMaskAcrossDimensions(maskIndex: u32, condition: F) +where + F: Fn(u32, u32) -> bool, +{ + TestMaskAcrossDimensionsImpl(maskIndex, true, 4, 9, 2, condition); +} diff --git a/src/qrcode/cpp_port/test/mod.rs b/src/qrcode/cpp_port/test/mod.rs index 9383384..5005ff1 100644 --- a/src/qrcode/cpp_port/test/mod.rs +++ b/src/qrcode/cpp_port/test/mod.rs @@ -6,3 +6,5 @@ mod QRFormatInformationTest; mod QRErrorCorrectionLevelTest; mod QRDecodedBitStreamParserTest; + +mod QRDataMaskTest; From 361130c3899adbacdcff1e422db72c17103a4c8c Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 14 Apr 2023 12:12:03 -0500 Subject: [PATCH 50/72] port QRBitMatrixParserTest --- .../cpp_port/test/QRBitMatrixParserTest.cpp | 73 ------------------- .../cpp_port/test/QRBitMatrixParserTest.rs | 69 ++++++++++++++++++ src/qrcode/cpp_port/test/mod.rs | 2 + 3 files changed, 71 insertions(+), 73 deletions(-) delete mode 100644 src/qrcode/cpp_port/test/QRBitMatrixParserTest.cpp create mode 100644 src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs diff --git a/src/qrcode/cpp_port/test/QRBitMatrixParserTest.cpp b/src/qrcode/cpp_port/test/QRBitMatrixParserTest.cpp deleted file mode 100644 index f8c5a93..0000000 --- a/src/qrcode/cpp_port/test/QRBitMatrixParserTest.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2017 Huy Cuong Nguyen - * Copyright 2008 ZXing authors -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "BitMatrix.h" -#include "BitMatrixIO.h" -#include "ByteArray.h" -#include "qrcode/QRBitMatrixParser.h" -#include "qrcode/QRFormatInformation.h" -#include "qrcode/QRVersion.h" - -#include "gtest/gtest.h" - -using namespace ZXing; -using namespace ZXing::QRCode; - -TEST(QRBitMatrixParserTest, MQRCodeM3L) -{ - const auto bitMatrix = ParseBitMatrix("XXXXXXX X X X X\n" - "X X X X \n" - "X XXX X XXXXXXX\n" - "X XXX X X X XX\n" - "X XXX X X XX\n" - "X X X X X X\n" - "XXXXXXX X XX \n" - " X X X\n" - "XXXXXX X X X\n" - " X XX XXX\n" - "XXX XX XXXX XXX\n" - " X X XXX X \n" - "X XXXXX XXX X X\n" - " X X X XXX \n" - "XXX XX X X XXXX\n", - 88, false); - - const auto version = ReadVersion(bitMatrix); - EXPECT_EQ(3, version->versionNumber()); - const auto format = ReadFormatInformation(bitMatrix, true); - const auto codewords = ReadCodewords(bitMatrix, *version, format); - EXPECT_EQ(17, codewords.size()); - EXPECT_EQ(0x0, codewords[10]); - EXPECT_EQ(0xd1, codewords[11]); -} - -TEST(QRBitMatrixParserTest, MQRCodeM3M) -{ - const auto bitMatrix = ParseBitMatrix("XXXXXXX X X X X\n" - "X X XX\n" - "X XXX X X XX XX\n" - "X XXX X X X \n" - "X XXX X XX XXXX\n" - "X X XX \n" - "XXXXXXX X XXXX\n" - " X XXX \n" - "X XX XX X X\n" - " X X XX \n" - "XX XX XXXXXXX\n" - " X X X\n" - "XX X X X \n" - " X X X \n" - "X X XXXX XXX\n", - 88, false); - - const auto version = ReadVersion(bitMatrix); - EXPECT_EQ(3, version->versionNumber()); - const auto format = ReadFormatInformation(bitMatrix, true); - const auto codewords = ReadCodewords(bitMatrix, *version, format); - EXPECT_EQ(17, codewords.size()); - EXPECT_EQ(0x0, codewords[8]); - EXPECT_EQ(0x89, codewords[9]); -} diff --git a/src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs b/src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs new file mode 100644 index 0000000..23563d4 --- /dev/null +++ b/src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs @@ -0,0 +1,69 @@ +/* + * Copyright 2017 Huy Cuong Nguyen + * Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +use crate::{qrcode::cpp_port::{ bitmatrix_parser::{ReadFormatInformation, ReadCodewords, ReadVersion}}, common::BitMatrix}; + +#[test] +fn MQRCodeM3L() +{ + let bitMatrix = + BitMatrix::parse_strings( +r"XXXXXXX X X X X +X X X X +X XXX X XXXXXXX +X XXX X X X XX +X XXX X X XX +X X X X X X +XXXXXXX X XX + X X X +XXXXXX X X X + X XX XXX +XXX XX XXXX XXX + X X XXX X +X XXXXX XXX X X + X X X XXX +XXX XX X X XXXX +", "X", " ").expect("parse must parse"); + + let version = ReadVersion(&bitMatrix).unwrap(); + assert_eq!(3, version.getVersionNumber()); + let format = ReadFormatInformation(&bitMatrix, true).expect("could not read format information"); + let codewords = ReadCodewords(&bitMatrix, version, &format).expect("could not read codewords"); + assert_eq!(17, codewords.len()); + assert_eq!(0x0, codewords[10]); + assert_eq!(0xd1, codewords[11]); +} + +#[test] +fn MQRCodeM3M() +{ + let bitMatrix = BitMatrix::parse_strings( +r"XXXXXXX X X X X +X X XX +X XXX X X XX XX +X XXX X X X +X XXX X XX XXXX +X X XX +XXXXXXX X XXXX + X XXX +X XX XX X X + X X XX +XX XX XXXXXXX + X X X +XX X X X + X X X +X X XXXX XXX +" +,"X", " ").unwrap(); + + let version = ReadVersion(&bitMatrix).unwrap(); + assert_eq!(3, version.getVersionNumber()); + let format = ReadFormatInformation(&bitMatrix, true).expect("could not read format information"); + let codewords = ReadCodewords(&bitMatrix, version, &format).expect("could not read codewords"); + assert_eq!(17, codewords.len()); + assert_eq!(0x0, codewords[8]); + assert_eq!(0x89, codewords[9]); +} diff --git a/src/qrcode/cpp_port/test/mod.rs b/src/qrcode/cpp_port/test/mod.rs index 5005ff1..b639d26 100644 --- a/src/qrcode/cpp_port/test/mod.rs +++ b/src/qrcode/cpp_port/test/mod.rs @@ -8,3 +8,5 @@ mod QRErrorCorrectionLevelTest; mod QRDecodedBitStreamParserTest; mod QRDataMaskTest; + +mod QRBitMatrixParserTest; \ No newline at end of file From d5c4d4de78069d4a4404e56882364665ca20164f Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sun, 16 Apr 2023 10:59:33 -0500 Subject: [PATCH 51/72] MQRDecoderTest --- src/qrcode/cpp_port/decoder.rs | 4 +- src/qrcode/cpp_port/test/MQRDecoderTest.cpp | 124 ------------------ src/qrcode/cpp_port/test/MQRDecoderTest.rs | 137 ++++++++++++++++++++ src/qrcode/cpp_port/test/mod.rs | 4 +- 4 files changed, 142 insertions(+), 127 deletions(-) delete mode 100644 src/qrcode/cpp_port/test/MQRDecoderTest.cpp create mode 100644 src/qrcode/cpp_port/test/MQRDecoderTest.rs diff --git a/src/qrcode/cpp_port/decoder.rs b/src/qrcode/cpp_port/decoder.rs index 0dd0e4d..c6637bf 100644 --- a/src/qrcode/cpp_port/decoder.rs +++ b/src/qrcode/cpp_port/decoder.rs @@ -35,7 +35,7 @@ pub fn CorrectErrors(codewordBytes: &mut [u8], numDataCodewords: u32) -> Result< let rs = ReedSolomonDecoder::new(get_predefined_genericgf( PredefinedGenericGF::QrCodeField256, )); - if rs.decode(&mut codewordsInts, numECCodewords)? == 0 + if rs.decode(&mut codewordsInts, numECCodewords)? != 0 // if (!ReedSolomonDecode(GenericGF::QRCodeField256(), codewordsInts, numECCodewords)) { return Ok(false); @@ -423,7 +423,7 @@ pub fn Decode(bits: &BitMatrix) -> Result> { let mut codewordBytes = dataBlock.getCodewords().to_vec(); let numDataCodewords = dataBlock.getNumDataCodewords() as usize; - if (!CorrectErrors(&mut codewordBytes, numDataCodewords as u32)?) { + if !CorrectErrors(&mut codewordBytes, numDataCodewords as u32)? { return Err(Exceptions::CHECKSUM); } diff --git a/src/qrcode/cpp_port/test/MQRDecoderTest.cpp b/src/qrcode/cpp_port/test/MQRDecoderTest.cpp deleted file mode 100644 index 25e7f70..0000000 --- a/src/qrcode/cpp_port/test/MQRDecoderTest.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2017 Huy Cuong Nguyen - * Copyright 2008 ZXing authors -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "qrcode/QRDecoder.h" - -#include "BitMatrix.h" -#include "BitMatrixIO.h" -#include "DecoderResult.h" - -#include "gtest/gtest.h" - -using namespace ZXing; -using namespace ZXing::QRCode; - -TEST(MQRDecoderTest, MQRCodeM3L) -{ - const auto bitMatrix = ParseBitMatrix("XXXXXXX X X X X\n" - "X X X X \n" - "X XXX X XXXXXXX\n" - "X XXX X X X XX\n" - "X XXX X X XX\n" - "X X X X X X\n" - "XXXXXXX X XX \n" - " X X X\n" - "XXXXXX X X X\n" - " X XX XXX\n" - "XXX XX XXXX XXX\n" - " X X XXX X \n" - "X XXXXX XXX X X\n" - " X X X XXX \n" - "XXX XX X X XXXX\n", - 88, false); - - const auto result = Decode(bitMatrix); - EXPECT_TRUE(result.isValid()); -} - -TEST(MQRDecoderTest, MQRCodeM3M) -{ - const auto bitMatrix = ParseBitMatrix("XXXXXXX X X X X\n" - "X X XX\n" - "X XXX X X XX XX\n" - "X XXX X X X \n" - "X XXX X XX XXXX\n" - "X X XX \n" - "XXXXXXX X XXXX\n" - " X XXX \n" - "X XX XX X X\n" - " X X XX \n" - "XX XX XXXXXXX\n" - " X X X\n" - "XX X X X \n" - " X X X \n" - "X X XXXX XXX\n", - 88, false); - - const auto result = Decode(bitMatrix); - EXPECT_TRUE(result.isValid()); -} - -TEST(MQRDecoderTest, MQRCodeM1) -{ - const auto bitMatrix = ParseBitMatrix("XXXXXXX X X\n" - "X X \n" - "X XXX X XXX\n" - "X XXX X XX\n" - "X XXX X X\n" - "X X XX \n" - "XXXXXXX X \n" - " X \n" - "XX X \n" - " X XXXXX X\n" - "X XXXXXX X\n", - 88, false); - const auto result = Decode(bitMatrix); - EXPECT_TRUE(result.isValid()); - EXPECT_EQ(L"123", result.text()); -} - -TEST(MQRDecoderTest, MQRCodeM1Error4Bits) -{ - const auto bitMatrix = ParseBitMatrix("XXXXXXX X X\n" - "X X XX\n" - "X XXX X X \n" - "X XXX X XX\n" - "X XXX X X\n" - "X X XX \n" - "XXXXXXX X \n" - " X \n" - "XX X \n" - " X XXXXXX \n" - "X XXXXXXX \n", - 88, false); - const auto result = Decode(bitMatrix); - EXPECT_EQ(Error::Checksum, result.error()); - EXPECT_TRUE(result.text().empty()); -} - -TEST(MQRDecoderTest, MQRCodeM4) -{ - const auto bitMatrix = ParseBitMatrix("XXXXXXX X X X X X\n" - "X X XX X XX\n" - "X XXX X X X XX\n" - "X XXX X XX XX XX\n" - "X XXX X X XXXXX\n" - "X X XX X\n" - "XXXXXXX XX X XX\n" - " X XX XX\n" - "X X XXX X XXX\n" - " XX X XX XX X \n" - "XX XXXX X XX XX\n" - " XX XX X XX XX\n" - "XXX XXX XXX XX XX\n" - " X X X XX X\n" - "X X XX XXXXX \n" - " X X X X X \n" - "X XXXXXXX X X X\n", - 88, false); - const auto result = Decode(bitMatrix); - EXPECT_TRUE(result.isValid()); -} diff --git a/src/qrcode/cpp_port/test/MQRDecoderTest.rs b/src/qrcode/cpp_port/test/MQRDecoderTest.rs new file mode 100644 index 0000000..5a1c1a8 --- /dev/null +++ b/src/qrcode/cpp_port/test/MQRDecoderTest.rs @@ -0,0 +1,137 @@ +/* + * Copyright 2017 Huy Cuong Nguyen + * Copyright 2008 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +use crate::{qrcode::cpp_port::decoder::Decode, common::BitMatrix, Exceptions}; + +#[test] +fn MQRCodeM3L() +{ + const CODE_STR : &str = +r"XXXXXXX X X X X +X X X X +X XXX X XXXXXXX +X XXX X X X XX +X XXX X X XX +X X X X X X +XXXXXXX X XX + X X X +XXXXXX X X X + X XX XXX +XXX XX XXXX XXX + X X XXX X +X XXXXX XXX X X + X X X XXX +XXX XX X X XXXX +"; + let bitMatrix = BitMatrix::parse_strings(CODE_STR, + "X", " ").unwrap(); + + let result = Decode(&bitMatrix).unwrap(); + assert!(result.isValid()); +} + +#[test] +fn MQRCodeM3M() +{ + const CODE_STR : &str = +r"XXXXXXX X X X X +X X XX +X XXX X X XX XX +X XXX X X X +X XXX X XX XXXX +X X XX +XXXXXXX X XXXX + X XXX +X XX XX X X + X X XX +XX XX XXXXXXX + X X X +XX X X X + X X X +X X XXXX XXX +"; + let bitMatrix = BitMatrix::parse_strings(CODE_STR, + "X", " ").unwrap(); + + let result = Decode(&bitMatrix).unwrap(); + assert!(result.isValid()); +} + +#[test] +fn MQRCodeM1() +{ + const CODE_STR : &str = +r"XXXXXXX X X +X X +X XXX X XXX +X XXX X XX +X XXX X X +X X XX +XXXXXXX X + X +XX X + X XXXXX X +X XXXXXX X +"; + let bitMatrix = BitMatrix::parse_strings(CODE_STR, + "X", " ").unwrap(); + let result = Decode(&bitMatrix).unwrap(); + assert!(result.isValid()); + assert_eq!("123", result.text()); +} + +#[test] +fn MQRCodeM1Error4Bits() +{ + const CODE_STR : &str = +r"XXXXXXX X X +X X XX +X XXX X X +X XXX X XX +X XXX X X +X X XX +XXXXXXX X + X +XX X + X XXXXXX +X XXXXXXX +"; + let bitMatrix = BitMatrix::parse_strings(CODE_STR, + "X", " ").unwrap(); + let result = Decode(&bitMatrix); + dbg!(&result); + assert!(matches!(result.err(), Some(Exceptions::ReedSolomonException(_)))); + // assert_eq!(Error::Checksum, result.error()); + // assert!(result.text().is_empty()); +} + +#[test] +fn MQRCodeM4() +{ + const CODE_STR : &str = +r"XXXXXXX X X X X X +X X XX X XX +X XXX X X X XX +X XXX X XX XX XX +X XXX X X XXXXX +X X XX X +XXXXXXX XX X XX + X XX XX +X X XXX X XXX + XX X XX XX X +XX XXXX X XX XX + XX XX X XX XX +XXX XXX XXX XX XX + X X X XX X +X X XX XXXXX + X X X X X +X XXXXXXX X X X +"; + let bitMatrix = BitMatrix::parse_strings(CODE_STR, + "X", " ").unwrap(); + let result = Decode(&bitMatrix).unwrap(); + assert!(result.isValid()); +} diff --git a/src/qrcode/cpp_port/test/mod.rs b/src/qrcode/cpp_port/test/mod.rs index b639d26..e48bc92 100644 --- a/src/qrcode/cpp_port/test/mod.rs +++ b/src/qrcode/cpp_port/test/mod.rs @@ -9,4 +9,6 @@ mod QRDecodedBitStreamParserTest; mod QRDataMaskTest; -mod QRBitMatrixParserTest; \ No newline at end of file +mod QRBitMatrixParserTest; + +mod MQRDecoderTest; \ No newline at end of file From d3ce9fcb90059b4e9229413979601e8027c4f63c Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 17 Apr 2023 16:47:57 -0500 Subject: [PATCH 52/72] still working on tests --- src/common/bit_array.rs | 12 ++ src/common/cpp_essentials/pattern.rs | 73 ++++++-- src/qrcode/cpp_port/qr_cpp_reader.rs | 9 + src/qrcode/cpp_port/test/MQRDecoderTest.rs | 78 ++++----- .../cpp_port/test/QRBitMatrixParserTest.rs | 63 ++++--- src/qrcode/cpp_port/test/QRWriterTest.cpp | 160 ------------------ src/qrcode/cpp_port/test/mod.rs | 2 +- 7 files changed, 148 insertions(+), 249 deletions(-) delete mode 100644 src/qrcode/cpp_port/test/QRWriterTest.cpp diff --git a/src/common/bit_array.rs b/src/common/bit_array.rs index 4155c37..1587a85 100644 --- a/src/common/bit_array.rs +++ b/src/common/bit_array.rs @@ -417,3 +417,15 @@ impl Into> for BitArray { // arr } } + +impl Into> for BitArray { + fn into(self) -> Vec { + let mut array = vec![false; self.size]; + + for pixel in 0..self.size { + array[pixel] = bool::from(self.get(pixel)); + } + + array + } +} diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index 53366df..6cb699e 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -88,7 +88,7 @@ impl<'a> PatternView<'_> { pub fn new(bars: &'a PatternRow) -> PatternView<'a> { PatternView { data: bars, - start: 0, + start: 1, count: bars.0.len(), current: 0, } @@ -116,11 +116,12 @@ impl<'a> PatternView<'_> { Some(*self.data.0.get(self.start)?) } pub fn end(&self) -> Option { - if self.start + self.count < self.data.0.len() { - Some(self.data.0[self.start + self.count]) - } else { - None - } + // if self.start + self.count < self.data.0.len() { + // Some(self.data.0[self.start + self.count]) + // } else { + // None + // } + Some(self.data.0.len() as PatternType) } // int sum(int n = 0) const { return std::accumulate(_data, _data + (n == 0 ? _size : n), 0); } @@ -130,7 +131,7 @@ impl<'a> PatternView<'_> { self.data .0 .iter() - .skip(self.start) + .skip(self.start + self.current) .take(n) .copied() .sum::() @@ -253,6 +254,21 @@ impl<'a> PatternView<'_> { pub fn extend(&mut self) { self.count = std::cmp::max(0, self.count - self.start) } + + fn try_get_index(&self, index: isize) -> Option { + if index.abs() > self.data.0.len() as isize { + return None; + } + if index >= 0 { + let fetch_spot = ((self.start + self.current) as isize + index) as usize; + return Some(self.data.0[fetch_spot]) + } + if index.abs() > (self.start + self.current) as isize { + return None; + } + let fetch_spot = ((self.start + self.current) as isize + index) as usize; + Some(self.data.0[fetch_spot]) + } } impl<'a> std::ops::Index for PatternView<'_> { @@ -263,12 +279,13 @@ impl<'a> std::ops::Index for PatternView<'_> { panic!("array index out of bounds") } if index >= 0 { - return &self[index as usize]; + let fetch_spot = ((self.start + self.current) as isize + index) as usize; + return &self.data.0[fetch_spot] } if index.abs() > self.start as isize { panic!("array index out of bounds") } - let fetch_spot = (self.start as isize + index) as usize; + let fetch_spot = ((self.start + self.current) as isize + index) as usize; &self.data.0[fetch_spot] } } @@ -410,6 +427,7 @@ pub fn IsPattern( relaxed_threshold: Option, ) -> f32 { let relaxed_threshold = relaxed_threshold.unwrap_or(false); + let mut module_size_ref = module_size_ref; let width = view.sum(Some(LEN)); if SUM > LEN && Into::::into(width) < SUM { @@ -424,6 +442,10 @@ pub fn IsPattern( return 0.0; } + if (module_size_ref == 0.0) { + module_size_ref = module_size; + } + let threshold = module_size_ref * (0.5 + (relaxed_threshold as u8) as f32 * 0.25) + 0.5; // the offset of 0.5 is to make the code less sensitive to quantization errors for small (near 1) module sizes. @@ -478,8 +500,13 @@ pub fn FindLeftGuardBy<'a, const LEN: usize, Pred: Fn(&PatternView, Option) return Ok(window); } let end = Into::::into(view.end().ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?) - minSize; - while window.start < end { - if isGuard(&window, Some(Into::::into(window[PREV_IDX]))) { + while (window.start + window.current) < end { + let prev = if let Some(v) = window.try_get_index(PREV_IDX) { + Some(v as f32) + } else { + None + }; + if isGuard(&window, prev) { return Ok(window); } @@ -496,7 +523,15 @@ pub fn FindLeftGuard<'a, const LEN: usize, const SUM: usize, const IS_SPARCE: bo minQuietZone: f32, ) -> Result> { FindLeftGuardBy::(view, std::cmp::max(minSize, LEN), |window, spaceInPixel| { - IsPattern(window, pattern, spaceInPixel, minQuietZone, 0.0, None) != 0.0 + // perform a fast plausability test for 1:1:3:1:1 pattern + // dbg!(window[2], 2 as PatternType * std::cmp::max(window[0], window[4])); + // dbg!(window[2] < std::cmp::max(window[1], window[3])); + if (window[2] < 2 as PatternType * std::cmp::max(window[0], window[4]) + || window[2] < std::cmp::max(window[1], window[3])) + { + return false; + } + return IsPattern(window, pattern, spaceInPixel, minQuietZone, 0.0, None) != 0.0; }) } @@ -568,11 +603,15 @@ impl> From for Color { } pub fn GetPatternRowTP(matrix: &BitMatrix, r: u32, pr: &mut PatternRow, transpose: bool) { - if (transpose) { - GetPatternRow(&Into::>::into(matrix.getCol(r)), pr) + let row = if transpose { + matrix.getCol(r) } else { - GetPatternRow(&Into::>::into(matrix.getRow(r)), pr) - } + matrix.getRow(r) + }; + + let pixel_states: Vec = row.into(); + + GetPatternRow(&pixel_states, pr) } pub fn GetPatternRow + Copy + Default + From>( @@ -602,6 +641,8 @@ pub fn GetPatternRow + Copy + Default + From>( count += 1; } + // dbg!(&p_row.0); + if count != 0 { p_row.0.push(count); } diff --git a/src/qrcode/cpp_port/qr_cpp_reader.rs b/src/qrcode/cpp_port/qr_cpp_reader.rs index 1993fb9..09eb44e 100644 --- a/src/qrcode/cpp_port/qr_cpp_reader.rs +++ b/src/qrcode/cpp_port/qr_cpp_reader.rs @@ -183,6 +183,15 @@ impl Reader for QrReader { } } + if detectorResult.is_err() { + for decode_function in [DetectPureQR, DetectPureMQR] { + detectorResult = decode_function(binImg); + if detectorResult.is_ok() { + break; + } + } + } + let detectorResult = detectorResult?; // let detectorResult: DetectorResult; diff --git a/src/qrcode/cpp_port/test/MQRDecoderTest.rs b/src/qrcode/cpp_port/test/MQRDecoderTest.rs index 5a1c1a8..b298150 100644 --- a/src/qrcode/cpp_port/test/MQRDecoderTest.rs +++ b/src/qrcode/cpp_port/test/MQRDecoderTest.rs @@ -4,13 +4,11 @@ */ // SPDX-License-Identifier: Apache-2.0 -use crate::{qrcode::cpp_port::decoder::Decode, common::BitMatrix, Exceptions}; +use crate::{common::BitMatrix, qrcode::cpp_port::decoder::Decode, Exceptions}; #[test] -fn MQRCodeM3L() -{ - const CODE_STR : &str = -r"XXXXXXX X X X X +fn MQRCodeM3L() { + const CODE_STR: &str = r"XXXXXXX X X X X X X X X X XXX X XXXXXXX X XXX X X X XX @@ -26,18 +24,15 @@ X XXXXX XXX X X X X X XXX XXX XX X X XXXX "; - let bitMatrix = BitMatrix::parse_strings(CODE_STR, - "X", " ").unwrap(); + let bitMatrix = BitMatrix::parse_strings(CODE_STR, "X", " ").unwrap(); - let result = Decode(&bitMatrix).unwrap(); - assert!(result.isValid()); + let result = Decode(&bitMatrix).unwrap(); + assert!(result.isValid()); } #[test] -fn MQRCodeM3M() -{ - const CODE_STR : &str = -r"XXXXXXX X X X X +fn MQRCodeM3M() { + const CODE_STR: &str = r"XXXXXXX X X X X X X XX X XXX X X XX XX X XXX X X X @@ -53,18 +48,15 @@ XX X X X X X X X X XXXX XXX "; - let bitMatrix = BitMatrix::parse_strings(CODE_STR, - "X", " ").unwrap(); + let bitMatrix = BitMatrix::parse_strings(CODE_STR, "X", " ").unwrap(); - let result = Decode(&bitMatrix).unwrap(); - assert!(result.isValid()); + let result = Decode(&bitMatrix).unwrap(); + assert!(result.isValid()); } #[test] -fn MQRCodeM1() -{ - const CODE_STR : &str = -r"XXXXXXX X X +fn MQRCodeM1() { + const CODE_STR: &str = r"XXXXXXX X X X X X XXX X XXX X XXX X XX @@ -76,18 +68,15 @@ XX X X XXXXX X X XXXXXX X "; - let bitMatrix = BitMatrix::parse_strings(CODE_STR, - "X", " ").unwrap(); - let result = Decode(&bitMatrix).unwrap(); - assert!(result.isValid()); - assert_eq!("123", result.text()); + let bitMatrix = BitMatrix::parse_strings(CODE_STR, "X", " ").unwrap(); + let result = Decode(&bitMatrix).unwrap(); + assert!(result.isValid()); + assert_eq!("123", result.text()); } #[test] -fn MQRCodeM1Error4Bits() -{ - const CODE_STR : &str = -r"XXXXXXX X X +fn MQRCodeM1Error4Bits() { + const CODE_STR: &str = r"XXXXXXX X X X X XX X XXX X X X XXX X XX @@ -99,20 +88,20 @@ XX X X XXXXXX X XXXXXXX "; - let bitMatrix = BitMatrix::parse_strings(CODE_STR, - "X", " ").unwrap(); - let result = Decode(&bitMatrix); - dbg!(&result); - assert!(matches!(result.err(), Some(Exceptions::ReedSolomonException(_)))); - // assert_eq!(Error::Checksum, result.error()); - // assert!(result.text().is_empty()); + let bitMatrix = BitMatrix::parse_strings(CODE_STR, "X", " ").unwrap(); + let result = Decode(&bitMatrix); + dbg!(&result); + assert!(matches!( + result.err(), + Some(Exceptions::ReedSolomonException(_)) + )); + // assert_eq!(Error::Checksum, result.error()); + // assert!(result.text().is_empty()); } #[test] -fn MQRCodeM4() -{ - const CODE_STR : &str = -r"XXXXXXX X X X X X +fn MQRCodeM4() { + const CODE_STR: &str = r"XXXXXXX X X X X X X X XX X XX X XXX X X X XX X XXX X XX XX XX @@ -130,8 +119,7 @@ X X XX XXXXX X X X X X X XXXXXXX X X X "; - let bitMatrix = BitMatrix::parse_strings(CODE_STR, - "X", " ").unwrap(); - let result = Decode(&bitMatrix).unwrap(); - assert!(result.isValid()); + let bitMatrix = BitMatrix::parse_strings(CODE_STR, "X", " ").unwrap(); + let result = Decode(&bitMatrix).unwrap(); + assert!(result.isValid()); } diff --git a/src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs b/src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs index 23563d4..f6610e3 100644 --- a/src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs +++ b/src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs @@ -4,14 +4,15 @@ */ // SPDX-License-Identifier: Apache-2.0 -use crate::{qrcode::cpp_port::{ bitmatrix_parser::{ReadFormatInformation, ReadCodewords, ReadVersion}}, common::BitMatrix}; +use crate::{ + common::BitMatrix, + qrcode::cpp_port::bitmatrix_parser::{ReadCodewords, ReadFormatInformation, ReadVersion}, +}; #[test] -fn MQRCodeM3L() -{ - let bitMatrix = - BitMatrix::parse_strings( -r"XXXXXXX X X X X +fn MQRCodeM3L() { + let bitMatrix = BitMatrix::parse_strings( + r"XXXXXXX X X X X X X X X X XXX X XXXXXXX X XXX X X X XX @@ -26,22 +27,26 @@ XXX XX XXXX XXX X XXXXX XXX X X X X X XXX XXX XX X X XXXX -", "X", " ").expect("parse must parse"); +", + "X", + " ", + ) + .expect("parse must parse"); - let version = ReadVersion(&bitMatrix).unwrap(); - assert_eq!(3, version.getVersionNumber()); - let format = ReadFormatInformation(&bitMatrix, true).expect("could not read format information"); - let codewords = ReadCodewords(&bitMatrix, version, &format).expect("could not read codewords"); - assert_eq!(17, codewords.len()); - assert_eq!(0x0, codewords[10]); - assert_eq!(0xd1, codewords[11]); + let version = ReadVersion(&bitMatrix).unwrap(); + assert_eq!(3, version.getVersionNumber()); + let format = + ReadFormatInformation(&bitMatrix, true).expect("could not read format information"); + let codewords = ReadCodewords(&bitMatrix, version, &format).expect("could not read codewords"); + assert_eq!(17, codewords.len()); + assert_eq!(0x0, codewords[10]); + assert_eq!(0xd1, codewords[11]); } #[test] -fn MQRCodeM3M() -{ - let bitMatrix = BitMatrix::parse_strings( -r"XXXXXXX X X X X +fn MQRCodeM3M() { + let bitMatrix = BitMatrix::parse_strings( + r"XXXXXXX X X X X X X XX X XXX X X XX XX X XXX X X X @@ -56,14 +61,18 @@ XX XX XXXXXXX XX X X X X X X X X XXXX XXX -" -,"X", " ").unwrap(); +", + "X", + " ", + ) + .unwrap(); - let version = ReadVersion(&bitMatrix).unwrap(); - assert_eq!(3, version.getVersionNumber()); - let format = ReadFormatInformation(&bitMatrix, true).expect("could not read format information"); - let codewords = ReadCodewords(&bitMatrix, version, &format).expect("could not read codewords"); - assert_eq!(17, codewords.len()); - assert_eq!(0x0, codewords[8]); - assert_eq!(0x89, codewords[9]); + let version = ReadVersion(&bitMatrix).unwrap(); + assert_eq!(3, version.getVersionNumber()); + let format = + ReadFormatInformation(&bitMatrix, true).expect("could not read format information"); + let codewords = ReadCodewords(&bitMatrix, version, &format).expect("could not read codewords"); + assert_eq!(17, codewords.len()); + assert_eq!(0x0, codewords[8]); + assert_eq!(0x89, codewords[9]); } diff --git a/src/qrcode/cpp_port/test/QRWriterTest.cpp b/src/qrcode/cpp_port/test/QRWriterTest.cpp deleted file mode 100644 index bb33cf4..0000000 --- a/src/qrcode/cpp_port/test/QRWriterTest.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* -* Copyright 2017 Huy Cuong Nguyen -* Copyright 2008 ZXing authors -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "BitMatrixIO.h" -#include "qrcode/QRWriter.h" -#include "qrcode/QRErrorCorrectionLevel.h" - -#include "gtest/gtest.h" -#include - -using namespace ZXing; -using namespace ZXing::QRCode; - -namespace { - - void DoTest(const std::wstring& contents, ErrorCorrectionLevel ecLevel, int resolution, const char* expected) { - Writer writer; - writer.setErrorCorrectionLevel(ecLevel); - auto matrix = writer.encode(contents, resolution, resolution); - auto actual = ToString(matrix, 'X', ' ', true); - EXPECT_EQ(matrix.width(), resolution); - EXPECT_EQ(matrix.height(), resolution); - EXPECT_EQ(actual, expected); - } - -} - -TEST(QRWriterTest, OverSize) -{ - // The QR should be multiplied up to fit, with extra padding if necessary - int bigEnough = 256; - Writer writer; - BitMatrix matrix = writer.encode(L"http://www.google.com/", bigEnough, bigEnough); - EXPECT_EQ(matrix.width(), bigEnough); - EXPECT_EQ(matrix.height(), bigEnough); - - // The QR will not fit in this size, so the matrix should come back bigger - int tooSmall = 20; - matrix = writer.encode(L"http://www.google.com/", tooSmall, tooSmall); - EXPECT_GT(matrix.width(), tooSmall); - EXPECT_GT(matrix.height(), tooSmall); - - // We should also be able to handle non-square requests by padding them - int strangeWidth = 500; - int strangeHeight = 100; - matrix = writer.encode(L"http://www.google.com/", strangeWidth, strangeHeight); - EXPECT_EQ(matrix.width(), strangeWidth); - EXPECT_EQ(matrix.height(), strangeHeight); - - int overflowSize = 0x1000000; - EXPECT_THROW(writer.encode(L"http://www.google.com/", overflowSize, overflowSize), std::invalid_argument); -} - -TEST(QRWriterTest, RegressionTest) -{ - DoTest(L"http://www.google.com/", ErrorCorrectionLevel::Medium, 99, - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X \n" - " X X X X X X X X X X X X \n" - " X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - ); -} diff --git a/src/qrcode/cpp_port/test/mod.rs b/src/qrcode/cpp_port/test/mod.rs index e48bc92..4dd3017 100644 --- a/src/qrcode/cpp_port/test/mod.rs +++ b/src/qrcode/cpp_port/test/mod.rs @@ -11,4 +11,4 @@ mod QRDataMaskTest; mod QRBitMatrixParserTest; -mod MQRDecoderTest; \ No newline at end of file +mod MQRDecoderTest; From 789719d3eba87e9c80c10f0a6f9c6c974c865525 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sat, 22 Apr 2023 11:44:37 -0500 Subject: [PATCH 53/72] adding result points to sampler --- Cargo.toml | 2 +- src/common/bit_array.rs | 8 +++ src/common/bit_matrix.rs | 17 +++++ .../cpp_essentials/concentric_finder.rs | 12 ++-- src/common/cpp_essentials/edge_tracer.rs | 2 +- .../fast_edge_to_edge_counter.rs | 61 ++++++++++------- src/common/cpp_essentials/pattern.rs | 52 +++++++++----- src/common/default_grid_sampler.rs | 12 ++-- src/common/grid_sampler.rs | 6 +- src/qrcode/cpp_port/detector.rs | 67 ++++++++++--------- 10 files changed, 148 insertions(+), 91 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e5286d9..17aa603 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ exclude = [ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -regex = "1.7.1" +regex = "1.8.0" fancy-regex = "0.11" once_cell = "1.17.1" encoding = "0.2" diff --git a/src/common/bit_array.rs b/src/common/bit_array.rs index 1587a85..b992bbb 100644 --- a/src/common/bit_array.rs +++ b/src/common/bit_array.rs @@ -83,6 +83,14 @@ impl BitArray { (self.bits[i / 32] & (1 << (i & 0x1F))) != 0 } + pub fn try_get(&self, i:usize) -> Option { + if (i / 32) >= self.bits.len() { + None + }else { +Some(self.get(i)) + } + } + /** * Sets bit i. * diff --git a/src/common/bit_matrix.rs b/src/common/bit_matrix.rs index d01ce76..f73d5d1 100644 --- a/src/common/bit_matrix.rs +++ b/src/common/bit_matrix.rs @@ -222,6 +222,10 @@ impl BitMatrix { // ((self.bits[offset] >> (x & 0x1f)) & 1) != 0 } + pub fn get_index>(&self, index: T) -> bool { + todo!() + } + #[inline(always)] fn get_offset(&self, y: u32, x: u32) -> usize { y as usize * self.row_size + (x as usize / 32) @@ -748,6 +752,19 @@ impl fmt::Display for BitMatrix { } } +impl From<&BitMatrix> for Vec { + fn from(value: &BitMatrix) -> Self { + let mut arr = vec![false;(value.width * value.height) as usize]; + for x in 0..value.width { + for y in 0..value.height { + let insert_pos = ((y * value.width) + x )as usize; + arr[insert_pos] = value.get(x, y); + } + } + arr + } +} + #[cfg(feature = "image")] /// This should only be used if you *know* that the `DynamicImage` is binary. impl TryFrom for BitMatrix { diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index b207141..13a9afe 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -68,7 +68,7 @@ pub fn ReadSymmetricPattern( for i in 0..=s_2 { // for (int i = 0; i <= s_2; ++i) { - if !next(cur, i) != 0 || !next(&mut cuo, -i) != 0 { + if !next(cur, i) == 0 || !next(&mut cuo, -i) == 0 { return None; } } @@ -124,14 +124,14 @@ pub fn CheckSymmetricPattern< } } - if !(IsPattern( + if IsPattern( &PatternView::new(&res), &FixedPattern::::with_reference(pattern), None, 0.0, 0.0, Some(RELAXED_THRESHOLD), - ) != 0.0) + ) == 0.0 { return 0; } @@ -220,9 +220,9 @@ pub fn CenterOfRing( n += 1; // find out if we come full circle around the center. 8 bits have to be set in the end. - neighbourMask |= 1 - << (4.0 + Point::dot(Point::bresenhamDirection(cur.p() - center), point(1.0, 3.0))) - as u32; + neighbourMask |= (1 + << (4.0 + Point::dot(Point::round(Point::bresenhamDirection(cur.p() - center)), point(1.0, 3.0))) + as u32); if !cur.stepAlongEdge(edgeDir, None) { return None; diff --git a/src/common/cpp_essentials/edge_tracer.rs b/src/common/cpp_essentials/edge_tracer.rs index 88a8a3c..309d7c7 100644 --- a/src/common/cpp_essentials/edge_tracer.rs +++ b/src/common/cpp_essentials/edge_tracer.rs @@ -182,7 +182,7 @@ impl<'a> EdgeTracer<'_> { EdgeTracer { img: image, p, - d, + d: Point::bresenhamDirection(d), //d, history: None, state: 0, } diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index 88ea88c..d1226c5 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -1,5 +1,3 @@ -use crate::common::BitArray; - use super::BitMatrixCursorTrait; pub struct FastEdgeToEdgeCounter { @@ -7,66 +5,77 @@ pub struct FastEdgeToEdgeCounter { // int stride = 0; // int stepsToBorder = 0; p: u32, // = nullptr; - stride: u32, // = 0; - stepsToBorder: u32, // = 0; - arr: BitArray, + stride: isize, // = 0; + stepsToBorder: i32, // = 0; + //arr: BitArray, + _arr: isize, + under_arry: Vec, } impl FastEdgeToEdgeCounter { pub fn new(cur: &T) -> Self { - let stride = cur.d().y as u32 * cur.img().width() as u32 + cur.d().x as u32; - let p = /*cur.img().getRow(cur.p().y).begin()*/ 0 + cur.p().x as u32; + let stride = cur.d().y as isize * cur.img().width() as isize + cur.d().x as isize; + let p = ((cur.p().y as isize * cur.img().width() as isize).abs() as i32 + cur.p().x as i32) as u32; // P IS SET WRONG IN REVERSE let maxStepsX = if cur.d().x != 0.0 { - (if cur.d().x > 0.0 { + if cur.d().x > 0.0 { cur.img().width() - 1 - cur.p().x as u32 } else { cur.p().x as u32 - }) + } } else { u32::MAX }; let maxStepsY = if cur.d().y != 0.0 { - (if cur.d().y > 0.0 { + if cur.d().y > 0.0 { cur.img().height() - 1 - cur.p().y as u32 } else { cur.p().y as u32 - }) + } } else { u32::MAX }; - let stepsToBorder = std::cmp::min(maxStepsX, maxStepsY); + let stepsToBorder = std::cmp::min(maxStepsX, maxStepsY) as i32; Self { p, stride, stepsToBorder, - arr: cur.img().getRow(cur.p().y as u32), + _arr: cur.p().y as isize * stride as isize, //cur.img().getRow(cur.p().y as u32), + under_arry: cur.img().into(), } } pub fn stepToNextEdge(&mut self, range: u32) -> u32 { - let maxSteps = std::cmp::min(self.stepsToBorder, range); + let maxSteps = std::cmp::min(self.stepsToBorder, range as i32); let mut steps = 0; loop { steps += 1; - if (steps > maxSteps) { - if (maxSteps == self.stepsToBorder) { - break; + if steps > maxSteps { + if maxSteps == self.stepsToBorder { + break false; } else { return 0; } } - if !(self.arr.get((self.p + steps * self.stride) as usize) - == self.arr.get((self.p + 0) as usize)) - { - break; - } - } // while (p[steps * stride] == p[0]); - self.p += steps * self.stride; + let idx_pt = self.get_array_check_index(steps); + + if !(self.under_arry[idx_pt] + == self.under_arry[self.p as usize]) + { + break true; + } + }; // while (p[steps * stride] == p[0]); + + self.p = (self.p as isize + (steps as isize * self.stride)).abs() as u32; self.stepsToBorder -= steps; - return steps; + return steps as u32; } -} + + #[inline(always)] + fn get_array_check_index(&self, steps: i32) -> usize { + (self.p as isize + (steps as isize * self.stride)) as usize + } +} \ No newline at end of file diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index 6cb699e..583b30c 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -60,17 +60,22 @@ impl From> for PatternRow { } } -impl<'a> Iterator for PatternView<'_> { +pub struct PatternViewIterator<'a> { + pattern_view: &'a PatternView<'a>, + current_position: usize, +} + +impl<'a> Iterator for PatternViewIterator<'_> { type Item = PatternType; fn next(&mut self) -> Option { - if self.current + 1 > self.count { + if self.current_position + 1 > self.pattern_view.count { return None; } - self.current += 1; + self.current_position += 1; - Some(*self.data.0.get(self.current + self.start)?) + Some(*self.pattern_view.data.0.get(self.current_position - 1 + self.pattern_view.start + self.pattern_view.current)?) } } @@ -82,7 +87,7 @@ pub struct PatternView<'a> { current: usize, } -impl<'a> PatternView<'_> { +impl<'a> PatternView<'a> { // A PatternRow always starts with the width of whitespace in front of the first black bar. // The first element of the PatternView is the first bar. pub fn new(bars: &'a PatternRow) -> PatternView<'a> { @@ -126,6 +131,10 @@ impl<'a> PatternView<'_> { // int sum(int n = 0) const { return std::accumulate(_data, _data + (n == 0 ? _size : n), 0); } pub fn sum(&self, n: Option) -> PatternType { + if self.count == self.data.len() { + return self.data.0.iter().sum::(); + } + let n = n.unwrap_or(self.count); self.data @@ -137,6 +146,10 @@ impl<'a> PatternView<'_> { .sum::() } + pub fn iter(&'a self) -> PatternViewIterator<'a> { + PatternViewIterator { pattern_view: self, current_position: 0 } + } + pub fn size(&self) -> usize { self.count } @@ -149,8 +162,7 @@ impl<'a> PatternView<'_> { self.data .0 .iter() - .skip(self.start) - .take(self.current) + .take(self.start + self.current) .copied() .sum::() /*return std::accumulate(_base, _data, 0);*/ } @@ -170,8 +182,8 @@ impl<'a> PatternView<'_> { } pub fn isValidWithN(&self, n: usize) -> bool { !self.data.0.is_empty() - && self.start <= self.current - && self.start + n <= (self.start + self.count) + && self.start <= self.current + self.start + && self.current + n <= (self.data.0.len()) /*return _data && _data >= _base && _data + n <= _end;*/ } pub fn isValid(&self) -> bool { @@ -201,7 +213,7 @@ impl<'a> PatternView<'_> { // return (acceptIfAtLastBar && isAtLastBar()) || _data[_size] >= sum() * scale; // } - pub fn subView(&'a self, offset: usize, size: Option) -> PatternView<'a> { + pub fn subView(&self, offset: usize, size: Option) -> PatternView<'a> { let mut size = size.unwrap_or(0); if size == 0 { size = self.count - offset; @@ -252,7 +264,7 @@ impl<'a> PatternView<'_> { } pub fn extend(&mut self) { - self.count = std::cmp::max(0, self.count - self.start) + self.count = std::cmp::max(0, self.data.len() - (self.current + self.start)) } fn try_get_index(&self, index: isize) -> Option { @@ -275,6 +287,10 @@ impl<'a> std::ops::Index for PatternView<'_> { type Output = PatternType; fn index(&self, index: isize) -> &Self::Output { + if self.count == self.data.len() { + return &self.data[index.abs() as usize] + } + if index > self.data.0.len() as isize { panic!("array index out of bounds") } @@ -294,6 +310,10 @@ impl<'a> std::ops::Index for PatternView<'_> { type Output = PatternType; fn index(&self, index: usize) -> &Self::Output { + if self.count == self.data.len() { + return &self.data[index] + } + if index > self.data.0.len() { panic!("array index out of bounds") } @@ -442,7 +462,7 @@ pub fn IsPattern( return 0.0; } - if (module_size_ref == 0.0) { + if module_size_ref == 0.0 { module_size_ref = module_size; } @@ -485,7 +505,7 @@ pub fn IsRightGuard( } pub fn FindLeftGuardBy<'a, const LEN: usize, Pred: Fn(&PatternView, Option) -> bool>( - view: &'a PatternView, + view: PatternView<'a>, minSize: usize, isGuard: Pred, ) -> Result> { @@ -517,7 +537,7 @@ pub fn FindLeftGuardBy<'a, const LEN: usize, Pred: Fn(&PatternView, Option) } pub fn FindLeftGuard<'a, const LEN: usize, const SUM: usize, const IS_SPARCE: bool>( - view: &'a PatternView, + view: PatternView<'a>, minSize: usize, pattern: &FixedPattern, minQuietZone: f32, @@ -526,8 +546,8 @@ pub fn FindLeftGuard<'a, const LEN: usize, const SUM: usize, const IS_SPARCE: bo // perform a fast plausability test for 1:1:3:1:1 pattern // dbg!(window[2], 2 as PatternType * std::cmp::max(window[0], window[4])); // dbg!(window[2] < std::cmp::max(window[1], window[3])); - if (window[2] < 2 as PatternType * std::cmp::max(window[0], window[4]) - || window[2] < std::cmp::max(window[1], window[3])) + if window[2] < 2 as PatternType * std::cmp::max(window[0], window[4]) + || window[2] < std::cmp::max(window[1], window[3]) { return false; } diff --git a/src/common/default_grid_sampler.rs b/src/common/default_grid_sampler.rs index 556642f..d9bc061 100644 --- a/src/common/default_grid_sampler.rs +++ b/src/common/default_grid_sampler.rs @@ -36,7 +36,7 @@ impl GridSampler for DefaultGridSampler { dimensionX: u32, dimensionY: u32, controls: &[SamplerControl], - ) -> Result { + ) -> Result<(BitMatrix,[Point;4])> { if dimensionX <= 0 || dimensionY <= 0 { return Err(Exceptions::NOT_FOUND); } @@ -89,11 +89,11 @@ impl GridSampler for DefaultGridSampler { Point::default() }; - let _tl = projectCorner(point(0.0, 0.0)); - let _tr = projectCorner(Point::from((dimensionX, 0))); - let _bl = projectCorner(Point::from((dimensionX, dimensionY))); - let _br = projectCorner(Point::from((0, dimensionX))); + let tl = projectCorner(point(0.0, 0.0)); + let tr = projectCorner(Point::from((dimensionX, 0))); + let bl = projectCorner(Point::from((dimensionX, dimensionY))); + let br = projectCorner(Point::from((0, dimensionX))); - Ok(bits) + Ok((bits, [tl,tr,bl,br])) } } diff --git a/src/common/grid_sampler.rs b/src/common/grid_sampler.rs index d368f47..2d69b24 100644 --- a/src/common/grid_sampler.rs +++ b/src/common/grid_sampler.rs @@ -95,7 +95,7 @@ pub trait GridSampler { dimensionY: u32, dst: Quadrilateral, src: Quadrilateral, - ) -> Result { + ) -> Result<(BitMatrix,[Point;4])> { let transform = PerspectiveTransform::quadrilateralToQuadrilateral(dst, src)?; self.sample_grid( @@ -112,7 +112,7 @@ pub trait GridSampler { dimensionX: u32, dimensionY: u32, controls: &[SamplerControl], - ) -> Result { + ) -> Result<(BitMatrix,[Point;4])> { if dimensionX == 0 || dimensionY == 0 { return Err(Exceptions::NOT_FOUND); } @@ -172,7 +172,7 @@ pub trait GridSampler { } // dbg!(bits.to_string()); - Ok(bits) + Ok((bits, [Point::default(), Point::default(), Point::default(), Point::default()])) } /** diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index 718791b..4324283 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -37,6 +37,8 @@ pub type FinderPatternSets = Vec; const PATTERN: FixedPattern<5, 7, false> = FixedPattern::new([1, 1, 3, 1, 1]); +/// Locate the finder patterns for the symbol. +/// This function can panic pub fn FindFinderPatterns(image: &BitMatrix, tryHarder: bool) -> FinderPatterns { const MIN_SKIP: u32 = 3; // 1 pixel/module times 3 modules/center const MAX_MODULES_FAST: u32 = 20 * 4 + 17; // support up to version 20 for mobile clients @@ -61,15 +63,12 @@ pub fn FindFinderPatterns(image: &BitMatrix, tryHarder: bool) -> FinderPatterns let mut next: PatternView = PatternView::new(&row); while { - if let Ok(next) = FindLeftGuard(&next, 0, &PATTERN, 0.5) { + if let Ok(up_next) = FindLeftGuard(next, 0, &PATTERN, 0.5) { + next = up_next; next.isValid() } else { false } - // let Ok(next) = FindLeftGuard(&next, 0, &PATTERN, 0.5) else { - // break; - // }; - // next.isValid() } { let p = point( next.pixelsInFront() as f32 @@ -90,7 +89,7 @@ pub fn FindFinderPatterns(image: &BitMatrix, tryHarder: bool) -> FinderPatterns image, &PATTERN.into(), p, - next.sum::() as i32 * 3, + next.iter().sum::() as i32 * 3, ); // 3 for very skewed samples // Reduce(next) * 3); // 3 for very skewed samples if (pattern.is_some()) { @@ -336,12 +335,13 @@ pub fn TraceLine(image: &BitMatrix, p: Point, d: Point, edge: i32) -> impl Regre for dir in [Direction::Left, Direction::Right] { // for (auto dir : {Direction::LEFT, Direction::RIGHT}) { let mut c = EdgeTracer::new(image, curI.p, curI.direction(dir)); - let stepCount = (Point::maxAbsComponent(cur.p - p)) as i32; + let mut stepCount = (Point::maxAbsComponent(cur.p - p)) as i32; loop { line.add(Point::centered(c.p)) .expect("could not add point on line"); - if !(--stepCount > 0 && c.stepAlongEdge(dir, Some(true))) { +stepCount -= 1; + if !(stepCount > 0 && c.stepAlongEdge(dir, Some(true))) { break; } } //while (--stepCount > 0 && c.stepAlongEdge(dir, true)); @@ -731,9 +731,10 @@ pub fn SampleQR(image: &BitMatrix, fp: &FinderPatternSet) -> Result Result Result Date: Sat, 22 Apr 2023 16:17:05 -0500 Subject: [PATCH 54/72] partial success on parse, missing symbology id --- src/aztec/detector.rs | 4 +++- src/common/bit_matrix.rs | 11 +++++++++-- .../cpp_essentials/fast_edge_to_edge_counter.rs | 9 ++++++--- src/common/default_grid_sampler.rs | 2 +- src/datamatrix/detector/datamatrix_detector.rs | 3 ++- .../detector/zxing_cpp_detector/cpp_new_detector.rs | 4 +++- src/maxicode/detector.rs | 2 +- src/qrcode/cpp_port/bitmatrix_parser.rs | 6 +++--- src/qrcode/cpp_port/decoder.rs | 2 +- src/qrcode/cpp_port/detector.rs | 4 ++-- src/qrcode/detector/qrcode_detector.rs | 5 +++-- 11 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/aztec/detector.rs b/src/aztec/detector.rs index b0bc4a2..3bb8766 100644 --- a/src/aztec/detector.rs +++ b/src/aztec/detector.rs @@ -469,7 +469,9 @@ impl<'a> Detector<'_> { point(low, high), ); - sampler.sample_grid_detailed(image, dimension, dimension, dst, quad) + let (res, _) = sampler.sample_grid_detailed(image, dimension, dimension, dst, quad)?; + + Ok(res) } /** diff --git a/src/common/bit_matrix.rs b/src/common/bit_matrix.rs index f73d5d1..197e12b 100644 --- a/src/common/bit_matrix.rs +++ b/src/common/bit_matrix.rs @@ -21,7 +21,7 @@ use std::fmt; use crate::common::Result; -use crate::{point, Exceptions, Point}; +use crate::{point, Exceptions, Point, point_i}; use super::BitArray; @@ -223,7 +223,14 @@ impl BitMatrix { } pub fn get_index>(&self, index: T) -> bool { - todo!() + self.get_point(self.calculate_point_from_index(index.into())) + } + + #[inline(always)] + fn calculate_point_from_index(&self, index: usize) -> Point { + let row = index / (self.getWidth() as usize); + let column = index % (self.getWidth() as usize); + point_i(column as u32, row as u32) } #[inline(always)] diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index d1226c5..95ad6a2 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -1,3 +1,5 @@ +use crate::common::BitMatrix; + use super::BitMatrixCursorTrait; pub struct FastEdgeToEdgeCounter { @@ -9,11 +11,11 @@ pub struct FastEdgeToEdgeCounter { stepsToBorder: i32, // = 0; //arr: BitArray, _arr: isize, - under_arry: Vec, + under_arry: Vec//&'a BitMatrix//, } impl FastEdgeToEdgeCounter { - pub fn new(cur: &T) -> Self { + pub fn new(cur: &T) -> FastEdgeToEdgeCounter { let stride = cur.d().y as isize * cur.img().width() as isize + cur.d().x as isize; let p = ((cur.p().y as isize * cur.img().width() as isize).abs() as i32 + cur.p().x as i32) as u32; // P IS SET WRONG IN REVERSE @@ -37,7 +39,7 @@ impl FastEdgeToEdgeCounter { }; let stepsToBorder = std::cmp::min(maxStepsX, maxStepsY) as i32; - Self { + FastEdgeToEdgeCounter { p, stride, stepsToBorder, @@ -63,6 +65,7 @@ impl FastEdgeToEdgeCounter { if !(self.under_arry[idx_pt] == self.under_arry[self.p as usize]) + // if !(self.under_arry.get_index(idx_pt) == self.under_arry.get_index(self.p as usize)) { break true; } diff --git a/src/common/default_grid_sampler.rs b/src/common/default_grid_sampler.rs index d9bc061..2c0b36a 100644 --- a/src/common/default_grid_sampler.rs +++ b/src/common/default_grid_sampler.rs @@ -89,7 +89,7 @@ impl GridSampler for DefaultGridSampler { Point::default() }; - let tl = projectCorner(point(0.0, 0.0)); + let tl = projectCorner(Point::default()); let tr = projectCorner(Point::from((dimensionX, 0))); let bl = projectCorner(Point::from((dimensionX, dimensionY))); let br = projectCorner(Point::from((0, dimensionX))); diff --git a/src/datamatrix/detector/datamatrix_detector.rs b/src/datamatrix/detector/datamatrix_detector.rs index 88a973c..63a877e 100644 --- a/src/datamatrix/detector/datamatrix_detector.rs +++ b/src/datamatrix/detector/datamatrix_detector.rs @@ -343,7 +343,8 @@ impl<'a> Detector<'_> { ); let src = Quadrilateral::new(topRight, topLeft, bottomRight, bottomLeft); - sampler.sample_grid_detailed(image, dimensionX, dimensionY, dst, src) + let(res,_)=sampler.sample_grid_detailed(image, dimensionX, dimensionY, dst, src)?; + Ok(res) } /** diff --git a/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs b/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs index 5d74e31..e09b8dc 100644 --- a/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs +++ b/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs @@ -233,8 +233,10 @@ fn Scan( CHECK!(res.is_ok()); + let (res, _) = res?; + return Ok(DatamatrixDetectorResult::new( - res?, + res, sourcePoints.points().to_vec(), )); } diff --git a/src/maxicode/detector.rs b/src/maxicode/detector.rs index 8f6546a..4294311 100644 --- a/src/maxicode/detector.rs +++ b/src/maxicode/detector.rs @@ -356,7 +356,7 @@ pub fn detect(image: &BitMatrix, try_harder: bool) -> Result Result= 0; y--) AppendBit(&mut formatInfoBits1, getBit(bitMatrix, 8, y, None)); } @@ -130,7 +130,7 @@ pub fn ReadQRCodewords( let mut bitsRead = 0; let dimension = bitMatrix.height(); // Read columns in pairs, from right to left - let mut x = dimension - 1; + let mut x = (dimension as i32) - 1; while x > 0 { // for (int x = dimension - 1; x > 0; x -= 2) { // Skip whole column with vertical timing pattern. @@ -143,7 +143,7 @@ pub fn ReadQRCodewords( let y = if readingUp { dimension - 1 - row } else { row }; for col in 0..2 { // for (int col = 0; col < 2; col++) { - let xx = x - col; + let xx = (x - col) as u32; // Ignore bits covered by the function pattern if (!functionPattern.get(xx, y)) { // Read a bit diff --git a/src/qrcode/cpp_port/decoder.rs b/src/qrcode/cpp_port/decoder.rs index c6637bf..0e6c14d 100644 --- a/src/qrcode/cpp_port/decoder.rs +++ b/src/qrcode/cpp_port/decoder.rs @@ -428,7 +428,7 @@ pub fn Decode(bits: &BitMatrix) -> Result> { } // resultIterator = std::copy_n(codewordBytes.begin(), numDataCodewords, resultIterator); - resultBytes[resultIterator..numDataCodewords] + resultBytes[resultIterator..(resultIterator+numDataCodewords)] .copy_from_slice(&codewordBytes[..numDataCodewords]); resultIterator += numDataCodewords; } diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index 4324283..a407b7a 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -747,8 +747,8 @@ pub fn SampleQR(image: &BitMatrix, fp: &FinderPatternSet) -> Result Detector<'_> { dimension: u32, ) -> Result { let sampler = DefaultGridSampler::default(); - sampler.sample_grid( + let (res, _) = sampler.sample_grid( image, dimension, dimension, &[SamplerControl::new(dimension, dimension, transform)], - ) + )?; + Ok(res) } /** From b4f99b3195712805e10973071afcbcf779b9bc2f Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sat, 22 Apr 2023 16:23:32 -0500 Subject: [PATCH 55/72] use internal BinaryBitmap instead of [bool] --- src/common/cpp_essentials/concentric_finder.rs | 3 ++- .../cpp_essentials/fast_edge_to_edge_counter.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index 13a9afe..5f8a17c 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -92,7 +92,8 @@ pub fn CheckSymmetricPattern< let mut range = range; let mut curFwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(cur); - let mut curBwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(&cur.turnedBack()); + let binding = cur.turnedBack(); + let mut curBwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(&binding); let centerFwd = curFwd.stepToNextEdge(range as u32) as i32; if centerFwd == 0 { diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index 95ad6a2..5d99997 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -2,7 +2,7 @@ use crate::common::BitMatrix; use super::BitMatrixCursorTrait; -pub struct FastEdgeToEdgeCounter { +pub struct FastEdgeToEdgeCounter<'a> { // const uint8_t* p = nullptr; // int stride = 0; // int stepsToBorder = 0; @@ -11,10 +11,10 @@ pub struct FastEdgeToEdgeCounter { stepsToBorder: i32, // = 0; //arr: BitArray, _arr: isize, - under_arry: Vec//&'a BitMatrix//, + under_arry: &'a BitMatrix//,Vec } -impl FastEdgeToEdgeCounter { +impl<'a> FastEdgeToEdgeCounter<'a> { pub fn new(cur: &T) -> FastEdgeToEdgeCounter { let stride = cur.d().y as isize * cur.img().width() as isize + cur.d().x as isize; let p = ((cur.p().y as isize * cur.img().width() as isize).abs() as i32 + cur.p().x as i32) as u32; // P IS SET WRONG IN REVERSE @@ -44,7 +44,7 @@ impl FastEdgeToEdgeCounter { stride, stepsToBorder, _arr: cur.p().y as isize * stride as isize, //cur.img().getRow(cur.p().y as u32), - under_arry: cur.img().into(), + under_arry: cur.img()//.into(), } } @@ -63,9 +63,9 @@ impl FastEdgeToEdgeCounter { let idx_pt = self.get_array_check_index(steps); - if !(self.under_arry[idx_pt] - == self.under_arry[self.p as usize]) - // if !(self.under_arry.get_index(idx_pt) == self.under_arry.get_index(self.p as usize)) + // if !(self.under_arry[idx_pt] + // == self.under_arry[self.p as usize]) + if !(self.under_arry.get_index(idx_pt) == self.under_arry.get_index(self.p as usize)) { break true; } From cb7050a5dd7916367c84c7e757e873e9362fdb83 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sun, 23 Apr 2023 11:23:56 -0500 Subject: [PATCH 56/72] build result --- src/common/cpp_essentials/decoder_result.rs | 55 +-------------------- src/common/eci_string_builder.rs | 10 ++-- src/qrcode/cpp_port/qr_cpp_reader.rs | 13 +++-- src/rxing_result.rs | 43 +++++++++++++++- 4 files changed, 58 insertions(+), 63 deletions(-) diff --git a/src/common/cpp_essentials/decoder_result.rs b/src/common/cpp_essentials/decoder_result.rs index a2e85fb..bccd94a 100644 --- a/src/common/cpp_essentials/decoder_result.rs +++ b/src/common/cpp_essentials/decoder_result.rs @@ -1,6 +1,6 @@ use std::{any::Any, rc::Rc}; -use crate::{common::ECIStringBuilder, Exceptions}; +use crate::{common::ECIStringBuilder, Exceptions, RXingResult}; use super::StructuredAppendInfo; @@ -64,59 +64,6 @@ where } } -// DecoderResult(const DecoderResult &) = delete; -// DecoderResult& operator=(const DecoderResult &) = delete; -// } - -// public: -// DecoderResult() = default; -// DecoderResult(Error error) : _error(std::move(error)) {} -// DecoderResult(Content&& bytes) : _content(std::move(bytes)) {} - -// DecoderResult(DecoderResult&&) noexcept = default; -// DecoderResult& operator=(DecoderResult&&) noexcept = default; - -// bool isValid(bool includeErrors = false) const -// { -// return includeErrors || (_content.symbology.code != 0 && !_error); -// } - -// const Content& content() const & { return _content; } -// Content&& content() && { return std::move(_content); } - -// to keep the unit tests happy for now: -// std::wstring text() const { return _content.utfW(); } -// std::string symbologyIdentifier() const { return _content.symbology.toString(false); } - -// Simple macro to set up getter/setter methods that save lots of boilerplate. -// It sets up a standard 'const & () const', 2 setters for setting lvalues via -// copy and 2 for setting rvalues via move. They are provided each to work -// either on lvalues (normal 'void (...)') or on rvalues (returning '*this' as -// rvalue). The latter can be used to optionally initialize a temporary in a -// return statement, e.g. -// return DecoderResult(bytes, text).setEcLevel(level); -// #define ZX_PROPERTY(TYPE, GETTER, SETTER) \ -// const TYPE& GETTER() const & { return _##GETTER; } \ -// TYPE&& GETTER() && { return std::move(_##GETTER); } \ -// void SETTER(const TYPE& v) & { _##GETTER = v; } \ -// void SETTER(TYPE&& v) & { _##GETTER = std::move(v); } \ -// DecoderResult&& SETTER(const TYPE& v) && { _##GETTER = v; return std::move(*this); } \ -// DecoderResult&& SETTER(TYPE&& v) && { _##GETTER = std::move(v); return std::move(*this); } - -// ZX_PROPERTY(std::string, ecLevel, setEcLevel) -// ZX_PROPERTY(int, lineCount, setLineCount) -// ZX_PROPERTY(int, versionNumber, setVersionNumber) -// ZX_PROPERTY(StructuredAppendInfo, structuredAppend, setStructuredAppend) - -// ZX_PROPERTY(Error, error, setError) - -// ZX_PROPERTY(bool, isMirrored, setIsMirrored) -// ZX_PROPERTY(bool, readerInit, setReaderInit) -// ZX_PROPERTY(std::shared_ptr, extra, setExtra) - -// #undef ZX_PROPERTY -// }; - impl DecoderResult where T: Copy + Clone + Default + Eq + PartialEq, diff --git a/src/common/eci_string_builder.rs b/src/common/eci_string_builder.rs index 2aee154..78f0941 100644 --- a/src/common/eci_string_builder.rs +++ b/src/common/eci_string_builder.rs @@ -21,7 +21,9 @@ // import java.nio.charset.Charset; // import java.nio.charset.StandardCharsets; -use std::{collections::HashMap, fmt}; +use std::{collections::HashMap, fmt::{self, Display}}; + +use crate::BarcodeFormat; use super::{CharacterSet, Eci, StringUtils}; @@ -126,10 +128,10 @@ impl ECIStringBuilder { /// Change the current encoding characterset, finding an eci to do so pub fn switch_encoding(&mut self, charset: CharacterSet) { //self.append_eci(Eci::from(charset)) - if (false && !self.has_eci) { + if false && !self.has_eci { self.eci_positions.clear(); } - if (false || !self.has_eci) + if false || !self.has_eci //{self.eci_positions.push_back({eci, Size(bytes)});} { self.append_eci(Eci::from(charset)) @@ -322,4 +324,4 @@ impl Default for SymbologyIdentifier { aiFlag: AIFlag::None, } } -} +} \ No newline at end of file diff --git a/src/qrcode/cpp_port/qr_cpp_reader.rs b/src/qrcode/cpp_port/qr_cpp_reader.rs index 09eb44e..1123feb 100644 --- a/src/qrcode/cpp_port/qr_cpp_reader.rs +++ b/src/qrcode/cpp_port/qr_cpp_reader.rs @@ -306,10 +306,15 @@ impl QrReader { } if (decoderResult.isValid()) { - results.push(RXingResult::new( - &decoderResult.content().to_string(), - decoderResult.content().bytes().to_vec(), - position.to_vec(), + // 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); diff --git a/src/rxing_result.rs b/src/rxing_result.rs index ea49223..f888935 100644 --- a/src/rxing_result.rs +++ b/src/rxing_result.rs @@ -16,7 +16,10 @@ use std::{collections::HashMap, fmt}; -use crate::{BarcodeFormat, Point, RXingResultMetadataType, RXingResultMetadataValue}; +use crate::{ + common::cpp_essentials::DecoderResult, BarcodeFormat, MetadataDictionary, Point, + RXingResultMetadataType, RXingResultMetadataValue, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -95,6 +98,44 @@ impl RXingResult { } } + pub fn with_decoder_result( + res: DecoderResult, + resultPoints: &[Point], + format: BarcodeFormat, + ) -> Self + where + T: Copy + Clone + Default + Eq + PartialEq, + { + let mut new_res = Self::new( + &res.text(), + res.content().bytes().to_vec(), + resultPoints.to_vec(), + format, + ); + + let mut meta_data = MetadataDictionary::new(); + meta_data.insert( + RXingResultMetadataType::ERROR_CORRECTION_LEVEL, + RXingResultMetadataValue::ErrorCorrectionLevel(res.ecLevel().to_owned()), + ); + meta_data.insert( + RXingResultMetadataType::STRUCTURED_APPEND_PARITY, + RXingResultMetadataValue::StructuredAppendParity(res.structuredAppend().count), + ); + meta_data.insert( + RXingResultMetadataType::STRUCTURED_APPEND_SEQUENCE, + RXingResultMetadataValue::StructuredAppendSequence(res.structuredAppend().index), + ); + meta_data.insert( + RXingResultMetadataType::SYMBOLOGY_IDENTIFIER, + RXingResultMetadataValue::SymbologyIdentifier(res.symbologyIdentifier()), + ); + + new_res.putAllMetadata(meta_data); + + new_res + } + /** * @return raw text encoded by the barcode */ From a811b56fbbd0b9cc9cf3b6b35ed944ed5cade59f Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sun, 23 Apr 2023 14:36:51 -0500 Subject: [PATCH 57/72] cargo fmt --- src/common/bit_array.rs | 8 ++-- src/common/bit_matrix.rs | 6 +-- .../cpp_essentials/concentric_finder.rs | 19 +++++---- .../fast_edge_to_edge_counter.rs | 14 +++---- src/common/cpp_essentials/matrix.rs | 4 +- src/common/cpp_essentials/pattern.rs | 19 ++++++--- src/common/default_grid_sampler.rs | 4 +- src/common/eci_string_builder.rs | 7 +++- src/common/grid_sampler.rs | 14 +++++-- .../detector/datamatrix_detector.rs | 2 +- src/qrcode/cpp_port/decoder.rs | 2 +- src/qrcode/cpp_port/detector.rs | 42 ++++++++----------- 12 files changed, 78 insertions(+), 63 deletions(-) diff --git a/src/common/bit_array.rs b/src/common/bit_array.rs index b992bbb..3f0bc4e 100644 --- a/src/common/bit_array.rs +++ b/src/common/bit_array.rs @@ -83,11 +83,11 @@ impl BitArray { (self.bits[i / 32] & (1 << (i & 0x1F))) != 0 } - pub fn try_get(&self, i:usize) -> Option { + pub fn try_get(&self, i: usize) -> Option { if (i / 32) >= self.bits.len() { - None - }else { -Some(self.get(i)) + None + } else { + Some(self.get(i)) } } diff --git a/src/common/bit_matrix.rs b/src/common/bit_matrix.rs index 197e12b..00259db 100644 --- a/src/common/bit_matrix.rs +++ b/src/common/bit_matrix.rs @@ -21,7 +21,7 @@ use std::fmt; use crate::common::Result; -use crate::{point, Exceptions, Point, point_i}; +use crate::{point, point_i, Exceptions, Point}; use super::BitArray; @@ -761,10 +761,10 @@ impl fmt::Display for BitMatrix { impl From<&BitMatrix> for Vec { fn from(value: &BitMatrix) -> Self { - let mut arr = vec![false;(value.width * value.height) as usize]; + let mut arr = vec![false; (value.width * value.height) as usize]; for x in 0..value.width { for y in 0..value.height { - let insert_pos = ((y * value.width) + x )as usize; + let insert_pos = ((y * value.width) + x) as usize; arr[insert_pos] = value.get(x, y); } } diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index 5f8a17c..1fe34b6 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -221,9 +221,12 @@ pub fn CenterOfRing( n += 1; // find out if we come full circle around the center. 8 bits have to be set in the end. - neighbourMask |= (1 - << (4.0 + Point::dot(Point::round(Point::bresenhamDirection(cur.p() - center)), point(1.0, 3.0))) - as u32); + neighbourMask |= 1 + << (4.0 + + Point::dot( + Point::round(Point::bresenhamDirection(cur.p() - center)), + point(1.0, 3.0), + )) as u32; if !cur.stepAlongEdge(edgeDir, None) { return None; @@ -325,8 +328,11 @@ pub fn CollectRingPoints( // find out if we come full circle around the center. 8 bits have to be set in the end. neighbourMask |= 1 - << (4.0 + Point::dot(Point::bresenhamDirection(cur.p - centerI), point(1.0, 3.0))) - as u32; + << (4.0 + + Point::dot( + Point::round(Point::bresenhamDirection(cur.p - centerI)), + point(1.0, 3.0), + )) as u32; if !cur.stepAlongEdge(edgeDir, None) { return Vec::default(); @@ -423,8 +429,7 @@ pub fn FitQadrilateralToPoints(center: Point, points: &mut [Point]) -> Option bool { - let mut m = f64::default(); - // let mut M = f64::default(); + let mut m; m = Point::distance(q[0], q[3]) as f64; //M = distance(q[0], q[3]); let mut M = m; diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index 5d99997..3ac072f 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -11,13 +11,14 @@ pub struct FastEdgeToEdgeCounter<'a> { stepsToBorder: i32, // = 0; //arr: BitArray, _arr: isize, - under_arry: &'a BitMatrix//,Vec + under_arry: &'a BitMatrix, //,Vec } impl<'a> FastEdgeToEdgeCounter<'a> { pub fn new(cur: &T) -> FastEdgeToEdgeCounter { let stride = cur.d().y as isize * cur.img().width() as isize + cur.d().x as isize; - let p = ((cur.p().y as isize * cur.img().width() as isize).abs() as i32 + cur.p().x as i32) as u32; // P IS SET WRONG IN REVERSE + let p = ((cur.p().y as isize * cur.img().width() as isize).abs() as i32 + cur.p().x as i32) + as u32; // P IS SET WRONG IN REVERSE let maxStepsX = if cur.d().x != 0.0 { if cur.d().x > 0.0 { @@ -44,7 +45,7 @@ impl<'a> FastEdgeToEdgeCounter<'a> { stride, stepsToBorder, _arr: cur.p().y as isize * stride as isize, //cur.img().getRow(cur.p().y as u32), - under_arry: cur.img()//.into(), + under_arry: cur.img(), //.into(), } } @@ -65,11 +66,10 @@ impl<'a> FastEdgeToEdgeCounter<'a> { // if !(self.under_arry[idx_pt] // == self.under_arry[self.p as usize]) - if !(self.under_arry.get_index(idx_pt) == self.under_arry.get_index(self.p as usize)) - { + if !(self.under_arry.get_index(idx_pt) == self.under_arry.get_index(self.p as usize)) { break true; } - }; // while (p[steps * stride] == p[0]); + } // while (p[steps * stride] == p[0]); self.p = (self.p as isize + (steps as isize * self.stride)).abs() as u32; self.stepsToBorder -= steps; @@ -81,4 +81,4 @@ impl<'a> FastEdgeToEdgeCounter<'a> { fn get_array_check_index(&self, steps: i32) -> usize { (self.p as isize + (steps as isize * self.stride)) as usize } -} \ No newline at end of file +} diff --git a/src/common/cpp_essentials/matrix.rs b/src/common/cpp_essentials/matrix.rs index bcdc084..d517b42 100644 --- a/src/common/cpp_essentials/matrix.rs +++ b/src/common/cpp_essentials/matrix.rs @@ -23,7 +23,7 @@ impl Matrix { } pub fn new(width: usize, height: usize) -> Result> { - if (width != 0 && height != 0) { + if (width != 0 && (width * height) / width as usize != height as usize) { return Err(Exceptions::illegal_argument_with( "invalid size: width * height is too big", )); @@ -64,7 +64,7 @@ impl Matrix { } pub fn get(&self, x: usize, y: usize) -> Option { - if x >= 0 && x < self.width && y >= 0 && y < self.height { + if x >= self.width || y >= self.height { None } else { Some(self.data[Self::get_offset(x, y, self.width)]) diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index 583b30c..b5bd65d 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -75,7 +75,11 @@ impl<'a> Iterator for PatternViewIterator<'_> { self.current_position += 1; - Some(*self.pattern_view.data.0.get(self.current_position - 1 + self.pattern_view.start + self.pattern_view.current)?) + Some( + *self.pattern_view.data.0.get( + self.current_position - 1 + self.pattern_view.start + self.pattern_view.current, + )?, + ) } } @@ -147,7 +151,10 @@ impl<'a> PatternView<'a> { } pub fn iter(&'a self) -> PatternViewIterator<'a> { - PatternViewIterator { pattern_view: self, current_position: 0 } + PatternViewIterator { + pattern_view: self, + current_position: 0, + } } pub fn size(&self) -> usize { @@ -273,7 +280,7 @@ impl<'a> PatternView<'a> { } if index >= 0 { let fetch_spot = ((self.start + self.current) as isize + index) as usize; - return Some(self.data.0[fetch_spot]) + return Some(self.data.0[fetch_spot]); } if index.abs() > (self.start + self.current) as isize { return None; @@ -288,7 +295,7 @@ impl<'a> std::ops::Index for PatternView<'_> { fn index(&self, index: isize) -> &Self::Output { if self.count == self.data.len() { - return &self.data[index.abs() as usize] + return &self.data[index.abs() as usize]; } if index > self.data.0.len() as isize { @@ -296,7 +303,7 @@ impl<'a> std::ops::Index for PatternView<'_> { } if index >= 0 { let fetch_spot = ((self.start + self.current) as isize + index) as usize; - return &self.data.0[fetch_spot] + return &self.data.0[fetch_spot]; } if index.abs() > self.start as isize { panic!("array index out of bounds") @@ -311,7 +318,7 @@ impl<'a> std::ops::Index for PatternView<'_> { fn index(&self, index: usize) -> &Self::Output { if self.count == self.data.len() { - return &self.data[index] + return &self.data[index]; } if index > self.data.0.len() { diff --git a/src/common/default_grid_sampler.rs b/src/common/default_grid_sampler.rs index 2c0b36a..8e80e2f 100644 --- a/src/common/default_grid_sampler.rs +++ b/src/common/default_grid_sampler.rs @@ -36,7 +36,7 @@ impl GridSampler for DefaultGridSampler { dimensionX: u32, dimensionY: u32, controls: &[SamplerControl], - ) -> Result<(BitMatrix,[Point;4])> { + ) -> Result<(BitMatrix, [Point; 4])> { if dimensionX <= 0 || dimensionY <= 0 { return Err(Exceptions::NOT_FOUND); } @@ -94,6 +94,6 @@ impl GridSampler for DefaultGridSampler { let bl = projectCorner(Point::from((dimensionX, dimensionY))); let br = projectCorner(Point::from((0, dimensionX))); - Ok((bits, [tl,tr,bl,br])) + Ok((bits, [tl, tr, bl, br])) } } diff --git a/src/common/eci_string_builder.rs b/src/common/eci_string_builder.rs index 78f0941..0d0e63f 100644 --- a/src/common/eci_string_builder.rs +++ b/src/common/eci_string_builder.rs @@ -21,7 +21,10 @@ // import java.nio.charset.Charset; // import java.nio.charset.StandardCharsets; -use std::{collections::HashMap, fmt::{self, Display}}; +use std::{ + collections::HashMap, + fmt::{self, Display}, +}; use crate::BarcodeFormat; @@ -324,4 +327,4 @@ impl Default for SymbologyIdentifier { aiFlag: AIFlag::None, } } -} \ No newline at end of file +} diff --git a/src/common/grid_sampler.rs b/src/common/grid_sampler.rs index 2d69b24..7678cb7 100644 --- a/src/common/grid_sampler.rs +++ b/src/common/grid_sampler.rs @@ -95,7 +95,7 @@ pub trait GridSampler { dimensionY: u32, dst: Quadrilateral, src: Quadrilateral, - ) -> Result<(BitMatrix,[Point;4])> { + ) -> Result<(BitMatrix, [Point; 4])> { let transform = PerspectiveTransform::quadrilateralToQuadrilateral(dst, src)?; self.sample_grid( @@ -112,7 +112,7 @@ pub trait GridSampler { dimensionX: u32, dimensionY: u32, controls: &[SamplerControl], - ) -> Result<(BitMatrix,[Point;4])> { + ) -> Result<(BitMatrix, [Point; 4])> { if dimensionX == 0 || dimensionY == 0 { return Err(Exceptions::NOT_FOUND); } @@ -172,7 +172,15 @@ pub trait GridSampler { } // dbg!(bits.to_string()); - Ok((bits, [Point::default(), Point::default(), Point::default(), Point::default()])) + Ok(( + bits, + [ + Point::default(), + Point::default(), + Point::default(), + Point::default(), + ], + )) } /** diff --git a/src/datamatrix/detector/datamatrix_detector.rs b/src/datamatrix/detector/datamatrix_detector.rs index 63a877e..e417ce9 100644 --- a/src/datamatrix/detector/datamatrix_detector.rs +++ b/src/datamatrix/detector/datamatrix_detector.rs @@ -343,7 +343,7 @@ impl<'a> Detector<'_> { ); let src = Quadrilateral::new(topRight, topLeft, bottomRight, bottomLeft); - let(res,_)=sampler.sample_grid_detailed(image, dimensionX, dimensionY, dst, src)?; + let (res, _) = sampler.sample_grid_detailed(image, dimensionX, dimensionY, dst, src)?; Ok(res) } diff --git a/src/qrcode/cpp_port/decoder.rs b/src/qrcode/cpp_port/decoder.rs index 0e6c14d..443e0ad 100644 --- a/src/qrcode/cpp_port/decoder.rs +++ b/src/qrcode/cpp_port/decoder.rs @@ -428,7 +428,7 @@ pub fn Decode(bits: &BitMatrix) -> Result> { } // resultIterator = std::copy_n(codewordBytes.begin(), numDataCodewords, resultIterator); - resultBytes[resultIterator..(resultIterator+numDataCodewords)] + resultBytes[resultIterator..(resultIterator + numDataCodewords)] .copy_from_slice(&codewordBytes[..numDataCodewords]); resultIterator += numDataCodewords; } diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index a407b7a..6fcfea1 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -64,7 +64,7 @@ pub fn FindFinderPatterns(image: &BitMatrix, tryHarder: bool) -> FinderPatterns while { if let Ok(up_next) = FindLeftGuard(next, 0, &PATTERN, 0.5) { - next = up_next; + next = up_next; next.isValid() } else { false @@ -340,7 +340,7 @@ pub fn TraceLine(image: &BitMatrix, p: Point, d: Point, edge: i32) -> impl Regre line.add(Point::centered(c.p)) .expect("could not add point on line"); -stepCount -= 1; + stepCount -= 1; if !(stepCount > 0 && c.stepAlongEdge(dir, Some(true))) { break; } @@ -414,13 +414,13 @@ pub fn LocateAlignmentPattern( ); // if we did not land on a black pixel the concentric pattern finder will fail - if (cor.is_none() || !image.get_point(cor.unwrap())) { + if cor.is_none() || !image.get_point(cor.unwrap()) { continue; } if let Some(cor1) = CenterOfRing(image, cor.unwrap(), moduleSize, 1, true) { if let Some(cor2) = CenterOfRing(image, cor.unwrap(), moduleSize * 3, -2, true) { - if (Point::distance(cor1, cor2) < moduleSize as f32 / 2.0) { + if Point::distance(cor1, cor2) < moduleSize as f32 / 2.0 { let res = (cor1 + cor2) / 2.0; // log(res, 3); return Some(res); @@ -442,13 +442,13 @@ pub fn ReadVersion( for mirror in [false, true] { // Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored) let mut versionBits = 0; - for y in (0..5).rev() { + for y in (0..=5).rev() { // for (int y = 5; y >= 0; --y) - for x in ((dimension - 11)..(dimension - 9)).rev() { + for x in ((dimension - 11)..=(dimension - 9)).rev() { // for (int x = dimension - 9; x >= dimension - 11; --x) { let mod_ = if mirror { point_i(y, x) } else { point_i(x, y) }; let pix = mod2Pix.transform_point((mod_).centered()); - if (!image.is_in(pix)) { + if !image.is_in(pix) { versionBits = -1; } else { AppendBit(&mut versionBits, image.get_point(pix)); @@ -563,14 +563,14 @@ pub fn SampleQR(image: &BitMatrix, fp: &FinderPatternSet) -> Result Result Result Result Date: Mon, 24 Apr 2023 17:42:40 -0500 Subject: [PATCH 58/72] partial test complete --- src/common/bit_source.rs | 4 +- .../cpp_essentials/concentric_finder.rs | 91 +++++++++++++++---- .../fast_edge_to_edge_counter.rs | 4 +- src/common/cpp_essentials/matrix.rs | 22 +++-- src/common/cpp_essentials/regression_line.rs | 5 + src/qrcode/cpp_port/decoder.rs | 25 ++--- src/qrcode/cpp_port/detector.rs | 4 +- src/rxing_result_point.rs | 7 ++ 8 files changed, 115 insertions(+), 47 deletions(-) diff --git a/src/common/bit_source.rs b/src/common/bit_source.rs index 22233df..cfb4174 100644 --- a/src/common/bit_source.rs +++ b/src/common/bit_source.rs @@ -147,7 +147,7 @@ impl BitSource { // Next read whole bytes if num_bits > 0 { while num_bits >= 8 { - result = (result << 8) | self.bytes[self.byte_offset] as u32; + result = (result << 8) | self.bytes[byte_offset] as u32; // result = ((result as u16) << 8) as u8 | (self.bytes[self.byte_offset]); byte_offset += 1; num_bits -= 8; @@ -158,7 +158,7 @@ impl BitSource { let bits_to_not_read = 8 - num_bits; let mask = (0xFF >> bits_to_not_read) << bits_to_not_read; result = (result << num_bits) - | ((self.bytes[self.byte_offset] & mask) as u32 >> bits_to_not_read); + | ((self.bytes[byte_offset] & mask) as u32 >> bits_to_not_read); bit_offset += num_bits; } } diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index 1fe34b6..0df0c69 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -305,7 +305,7 @@ pub fn CollectRingPoints( edgeIndex: i32, backup: bool, ) -> Vec { - let centerI = center.round(); + let centerI = center.floor(); let radius = range; let mut cur = EdgeTracer::new(image, centerI, point(0.0, 1.0)); if cur.stepToEdge(Some(edgeIndex), Some(radius), Some(backup)) == 0 { @@ -362,17 +362,20 @@ pub fn FitQadrilateralToPoints(center: Point, points: &mut [Point]) -> Option Option Option Option Option) -> Option { +// let dist2center = |a, b| Point::distance(a, center) < Point::distance(b, center); + +// // Rotate points such that the first one is the furthest away from the center (hence, a corner) +// let mut points = points; +// points.rotate_left(points.iter().position(|p| dist2center(p, center)).unwrap()); + +// let mut corners = [&points[0]; 4]; +// corners[0] = &points[0]; + +// // Find the opposite corner by looking for the farthest point near the opposite point +// let opposite_corner = points.iter().take(points.len() / 2).max_by(|p| dist2center(p, corners[0])); +// corners[2] = opposite_corner; + +// // Find the two in between corners by looking for the points farthest from the long diagonal +// let dist2diagonal = |l: &RegressionLine, a| l.distance(a); +// corners[1] = points.iter().take(points.len() / 2).max_by(|p| dist2diagonal(&RegressionLine(*corners[0], *corners[2]), *p)); +// corners[3] = points.iter().skip(points.len() / 2).max_by(|p| dist2diagonal(&RegressionLine(*corners[0], *corners[2]), *p)); + +// let lines = [ +// RegressionLine::new(corners[0], corners[1]), +// RegressionLine::new(corners[1], corners[2]), +// RegressionLine::new(corners[2], corners[3]), +// RegressionLine::new(corners[3], corners[0]), +// ]; + +// if lines.iter().any(|l| !l.is_valid()) { +// return None; +// } + +// let mut res = QuadrilateralF::new(); +// for i in 0..4 { +// res[i] = intersect(lines[i], lines[(i + 1) % 4]); +// } + +// Some(res) +// } + pub fn QuadrilateralIsPlausibleSquare(q: &Quadrilateral, lineIndex: usize) -> bool { let mut m; diff --git a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index 3ac072f..dba41a6 100644 --- a/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -56,7 +56,7 @@ impl<'a> FastEdgeToEdgeCounter<'a> { steps += 1; if steps > maxSteps { if maxSteps == self.stepsToBorder { - break false; + break; } else { return 0; } @@ -67,7 +67,7 @@ impl<'a> FastEdgeToEdgeCounter<'a> { // if !(self.under_arry[idx_pt] // == self.under_arry[self.p as usize]) if !(self.under_arry.get_index(idx_pt) == self.under_arry.get_index(self.p as usize)) { - break true; + break; } } // while (p[steps * stride] == p[0]); diff --git a/src/common/cpp_essentials/matrix.rs b/src/common/cpp_essentials/matrix.rs index d517b42..a1c1cfe 100644 --- a/src/common/cpp_essentials/matrix.rs +++ b/src/common/cpp_essentials/matrix.rs @@ -5,12 +5,12 @@ use crate::{Exceptions, Point}; pub struct Matrix { width: usize, height: usize, - data: Vec, + data: Vec>, } impl Matrix { - pub fn with_data(width: usize, height: usize, data: Vec) -> Result> { - if (width != 0 && data.len() / width as usize != height as usize) { + pub fn with_data(width: usize, height: usize, data: Vec>) -> Result> { + if width != 0 && data.len() / width as usize != height as usize { return Err(Exceptions::illegal_argument_with( "invalid size: width * height is too big", )); @@ -31,7 +31,7 @@ impl Matrix { Ok(Self { width, height, - data: vec![T::default(); width * height], + data: vec![None; width * height], }) } @@ -66,13 +66,15 @@ impl Matrix { pub fn get(&self, x: usize, y: usize) -> Option { if x >= self.width || y >= self.height { None - } else { - Some(self.data[Self::get_offset(x, y, self.width)]) + } else if let Some(Some(d)) = self.data.get(Self::get_offset(x, y, self.width)){ + Some(*d) + }else { + None } } pub fn set(&mut self, x: usize, y: usize, value: T) -> T { - self.data[Self::get_offset(x, y, self.width)] = value; + self.data[Self::get_offset(x, y, self.width)] = Some(value); self.get(x, y).unwrap() } @@ -84,7 +86,7 @@ impl Matrix { self.set(p.x as usize, p.y as usize, value) } - pub fn data(&self) -> &[T] { + pub fn data(&self) -> &[Option] { &self.data } @@ -97,10 +99,10 @@ impl Matrix { // } pub fn clear_with(&mut self, value: T) { - self.data.fill(value) + self.data.fill(Some(value)) } pub fn clear(&mut self) { - self.data.fill(T::default()) + self.data.fill(None) } } diff --git a/src/common/cpp_essentials/regression_line.rs b/src/common/cpp_essentials/regression_line.rs index 8847389..6e28d10 100644 --- a/src/common/cpp_essentials/regression_line.rs +++ b/src/common/cpp_essentials/regression_line.rs @@ -228,4 +228,9 @@ impl RegressionLine { new_rl.evaluate(&[point1, point2]); new_rl } + pub fn with_point_slice(points: &[Point]) -> Self { + let mut new_rl = RegressionLine::default(); + new_rl.evaluate(points); + new_rl + } } diff --git a/src/qrcode/cpp_port/decoder.rs b/src/qrcode/cpp_port/decoder.rs index 443e0ad..042a356 100644 --- a/src/qrcode/cpp_port/decoder.rs +++ b/src/qrcode/cpp_port/decoder.rs @@ -35,11 +35,14 @@ pub fn CorrectErrors(codewordBytes: &mut [u8], numDataCodewords: u32) -> Result< let rs = ReedSolomonDecoder::new(get_predefined_genericgf( PredefinedGenericGF::QrCodeField256, )); - if rs.decode(&mut codewordsInts, numECCodewords)? != 0 - // if (!ReedSolomonDecode(GenericGF::QRCodeField256(), codewordsInts, numECCodewords)) - { - return Ok(false); - } + + rs.decode(&mut codewordsInts, numECCodewords)?; + + // if rs.decode(&mut codewordsInts, numECCodewords)? != 0 + // // if (!ReedSolomonDecode(GenericGF::QRCodeField256(), codewordsInts, numECCodewords)) + // { + // return Ok(false); + // } // Copy back into array of bytes -- only need to worry about the bytes that were data // We don't care about errors in the error-correction codewords @@ -298,9 +301,9 @@ pub fn DecodeBitStream( let modeBitLength = Mode::get_codec_mode_bits_length(version); let res = (|| { - while (!IsEndOfStream(&mut bits, version)?) { + while !IsEndOfStream(&mut bits, version)? { let mode: Mode; - if (modeBitLength == 0) { + if modeBitLength == 0 { mode = Mode::NUMERIC; // MicroQRCode version 1 is always NUMERIC and modeBitLength is 0 } else { mode = Mode::CodecModeForBits( @@ -309,7 +312,7 @@ pub fn DecodeBitStream( )?; } - match (mode) { + match mode { Mode::FNC1_FIRST_POSITION => { // if (!result.empty()) // uncomment to enforce specification // throw FormatError("GS1 Indicator (FNC1 in first position) at illegal position"); @@ -317,14 +320,14 @@ pub fn DecodeBitStream( result.symbology.aiFlag = AIFlag::GS1; // In Alphanumeric mode undouble doubled '%' and treat single '%' as } Mode::FNC1_SECOND_POSITION => { - if (!result.is_empty()) { + if !result.is_empty() { return Err(Exceptions::format_with("AIM Application Indicator (FNC1 in second position) at illegal position")); // throw FormatError("AIM Application Indicator (FNC1 in second position) at illegal position"); } result.symbology.modifier = b'5'; // As above // ISO/IEC 18004:2015 7.4.8.3 AIM Application Indicator (FNC1 in second position), "00-99" or "A-Za-z" let appInd = bits.readBits(8)?; - if (appInd < 100) + if appInd < 100 // "00-09" { result += @@ -367,7 +370,7 @@ pub fn DecodeBitStream( // "Normal" QR code modes: // How many characters will follow, encoded in this mode? let count = bits.readBits(mode.CharacterCountBits(version) as usize)?; - match (mode) { + match mode { Mode::NUMERIC => DecodeNumericSegment(&mut bits, count, &mut result)?, Mode::ALPHANUMERIC => { DecodeAlphanumericSegment(&mut bits, count, &mut result)? diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index 6fcfea1..c5ffb52 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -714,8 +714,8 @@ pub fn SampleQR(image: &BitMatrix, fp: &FinderPatternSet) -> Result Self { + Self { + x: self.x.floor(), + y: self.y.floor() + } + } } impl From<&(f32, f32)> for Point { From 483539a1947f6534c1002e1e32938cd616df3d24 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 24 Apr 2023 17:49:04 -0500 Subject: [PATCH 59/72] fix pattern test --- src/common/cpp_essentials/concentric_finder.rs | 15 ++++++++++----- src/common/cpp_essentials/matrix.rs | 4 ++-- src/common/cpp_essentials/pattern.rs | 2 +- src/qrcode/cpp_port/detector.rs | 5 ++++- src/rxing_result_point.rs | 2 +- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index 0df0c69..c0479c6 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -412,7 +412,12 @@ pub fn FitQadrilateralToPoints(center: Point, points: &mut [Point]) -> Option Matrix { pub fn get(&self, x: usize, y: usize) -> Option { if x >= self.width || y >= self.height { None - } else if let Some(Some(d)) = self.data.get(Self::get_offset(x, y, self.width)){ + } else if let Some(Some(d)) = self.data.get(Self::get_offset(x, y, self.width)) { Some(*d) - }else { + } else { None } } diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index b5bd65d..6fcd9c2 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -163,7 +163,7 @@ impl<'a> PatternView<'a> { // index is the number of bars and spaces from the first bar to the current position pub fn index(&self) -> usize { - self.current - self.start /*return narrow_cast(_data - _base) - 1;*/ + self.current /*return narrow_cast(_data - _base) - 1;*/ } pub fn pixelsInFront(&self) -> PatternType { self.data diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index c5ffb52..6bbead9 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -715,7 +715,10 @@ pub fn SampleQR(image: &BitMatrix, fp: &FinderPatternSet) -> Result Self { Self { x: self.x.floor(), - y: self.y.floor() + y: self.y.floor(), } } } From 31413b3044ade3eb2880a4e43221e398593ad396 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 24 Apr 2023 18:16:48 -0500 Subject: [PATCH 60/72] fix errant has_eci --- src/common/cpp_essentials/decoder_result.rs | 3 +++ src/common/eci.rs | 2 +- src/common/eci_string_builder.rs | 18 ++++++++++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/common/cpp_essentials/decoder_result.rs b/src/common/cpp_essentials/decoder_result.rs index bccd94a..0897d76 100644 --- a/src/common/cpp_essentials/decoder_result.rs +++ b/src/common/cpp_essentials/decoder_result.rs @@ -51,6 +51,9 @@ where pub fn with_eci_string_builder(src: ECIStringBuilder) -> Self { let mut new_self = Self::default(); new_self.content = src; + + + new_self } diff --git a/src/common/eci.rs b/src/common/eci.rs index 3d8e6ce..699a529 100644 --- a/src/common/eci.rs +++ b/src/common/eci.rs @@ -6,7 +6,7 @@ use super::CharacterSet; use crate::common::Result; -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum Eci { Unknown = -1, Cp437 = 2, // obsolete diff --git a/src/common/eci_string_builder.rs b/src/common/eci_string_builder.rs index 0d0e63f..c5166bb 100644 --- a/src/common/eci_string_builder.rs +++ b/src/common/eci_string_builder.rs @@ -22,11 +22,11 @@ // import java.nio.charset.StandardCharsets; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, fmt::{self, Display}, }; -use crate::BarcodeFormat; +use crate::{pdf417::decoder::ec, BarcodeFormat}; use super::{CharacterSet, Eci, StringUtils}; @@ -125,6 +125,12 @@ impl ECIStringBuilder { } self.eci_positions.push((eci, self.bytes.len(), 0)); + + let ecis = self.list_ecis(); + + if ecis.len() <= 1 && (ecis.contains(&Eci::Unknown)) { + self.has_eci = false; + } } } @@ -263,6 +269,14 @@ impl ECIStringBuilder { self } + + pub fn list_ecis(&self) -> HashSet { + let mut hs = HashSet::new(); + self.eci_positions.iter().for_each(|pos| { + hs.insert(pos.0); + }); + hs + } } impl fmt::Display for ECIStringBuilder { From 60c14ea556a45303c5d41057ea0592c25c92c595 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 24 Apr 2023 18:58:30 -0500 Subject: [PATCH 61/72] copy over c++ tests for mqr and cpp --- .../blackbox/cpp/microqrcode-1/1.png | Bin 0 -> 8513 bytes .../blackbox/cpp/microqrcode-1/1.txt | 1 + .../blackbox/cpp/microqrcode-1/11.jpg | Bin 0 -> 29581 bytes .../blackbox/cpp/microqrcode-1/11.txt | 1 + .../blackbox/cpp/microqrcode-1/12.jpg | Bin 0 -> 19328 bytes .../blackbox/cpp/microqrcode-1/12.txt | 1 + .../blackbox/cpp/microqrcode-1/3.jpg | Bin 0 -> 27818 bytes .../blackbox/cpp/microqrcode-1/3.txt | 1 + .../blackbox/cpp/microqrcode-1/7.jpg | Bin 0 -> 27342 bytes .../blackbox/cpp/microqrcode-1/7.txt | 1 + .../blackbox/cpp/microqrcode-1/9.jpg | Bin 0 -> 9245 bytes .../blackbox/cpp/microqrcode-1/9.txt | 1 + .../blackbox/cpp/microqrcode-1/M1-Numeric.png | Bin 0 -> 131 bytes .../blackbox/cpp/microqrcode-1/M1-Numeric.txt | 1 + .../blackbox/cpp/microqrcode-1/M2-Alpha.png | Bin 0 -> 146 bytes .../blackbox/cpp/microqrcode-1/M2-Alpha.txt | 1 + .../blackbox/cpp/microqrcode-1/M2-Numeric.png | Bin 0 -> 144 bytes .../blackbox/cpp/microqrcode-1/M2-Numeric.txt | 1 + .../blackbox/cpp/microqrcode-1/M3-Binary.png | Bin 0 -> 152 bytes .../blackbox/cpp/microqrcode-1/M3-Binary.txt | 1 + .../blackbox/cpp/microqrcode-1/M3-Kanji.png | Bin 0 -> 153 bytes .../blackbox/cpp/microqrcode-1/M3-Kanji.txt | 1 + .../blackbox/cpp/microqrcode-1/M3-Mixed.png | Bin 0 -> 150 bytes .../blackbox/cpp/microqrcode-1/M3-Mixed.txt | 1 + .../blackbox/cpp/microqrcode-1/M4-Binary.png | Bin 0 -> 168 bytes .../blackbox/cpp/microqrcode-1/M4-Binary.txt | 1 + .../blackbox/cpp/microqrcode-1/M4-Mixed.png | Bin 0 -> 167 bytes .../blackbox/cpp/microqrcode-1/M4-Mixed.txt | 1 + .../cpp/microqrcode-1/MQR-needs-br-update.jpg | Bin 0 -> 2002 bytes .../cpp/microqrcode-1/MQR-needs-br-update.txt | 1 + .../cpp/microqrcode-1/NoQuietZone.png | Bin 0 -> 112 bytes .../cpp/microqrcode-1/NoQuietZone.txt | 1 + test_resources/blackbox/cpp/qrcode-1/1.png | Bin 0 -> 233653 bytes test_resources/blackbox/cpp/qrcode-1/1.txt | 1 + test_resources/blackbox/cpp/qrcode-1/11.png | Bin 0 -> 218288 bytes test_resources/blackbox/cpp/qrcode-1/11.txt | 1 + test_resources/blackbox/cpp/qrcode-1/12.png | Bin 0 -> 227350 bytes test_resources/blackbox/cpp/qrcode-1/12.txt | 1 + test_resources/blackbox/cpp/qrcode-1/13.png | Bin 0 -> 239514 bytes test_resources/blackbox/cpp/qrcode-1/13.txt | 1 + test_resources/blackbox/cpp/qrcode-1/14.png | Bin 0 -> 230207 bytes test_resources/blackbox/cpp/qrcode-1/14.txt | 1 + test_resources/blackbox/cpp/qrcode-1/15.png | Bin 0 -> 254302 bytes test_resources/blackbox/cpp/qrcode-1/15.txt | 1 + test_resources/blackbox/cpp/qrcode-1/16.png | Bin 0 -> 322970 bytes test_resources/blackbox/cpp/qrcode-1/16.txt | 4 ++ test_resources/blackbox/cpp/qrcode-1/17.png | Bin 0 -> 295357 bytes test_resources/blackbox/cpp/qrcode-1/17.txt | 4 ++ test_resources/blackbox/cpp/qrcode-1/18.png | Bin 0 -> 328851 bytes test_resources/blackbox/cpp/qrcode-1/18.txt | 4 ++ test_resources/blackbox/cpp/qrcode-1/19.png | Bin 0 -> 219621 bytes test_resources/blackbox/cpp/qrcode-1/19.txt | 4 ++ test_resources/blackbox/cpp/qrcode-1/2.png | Bin 0 -> 208045 bytes test_resources/blackbox/cpp/qrcode-1/2.txt | 1 + test_resources/blackbox/cpp/qrcode-1/20.png | Bin 0 -> 237295 bytes test_resources/blackbox/cpp/qrcode-1/20.txt | 4 ++ test_resources/blackbox/cpp/qrcode-1/4.png | Bin 0 -> 204480 bytes test_resources/blackbox/cpp/qrcode-1/4.txt | 1 + test_resources/blackbox/cpp/qrcode-1/7.png | Bin 0 -> 225942 bytes test_resources/blackbox/cpp/qrcode-1/7.txt | 1 + test_resources/blackbox/cpp/qrcode-1/8.png | Bin 0 -> 225677 bytes test_resources/blackbox/cpp/qrcode-1/8.txt | 1 + test_resources/blackbox/cpp/qrcode-1/9.png | Bin 0 -> 207849 bytes test_resources/blackbox/cpp/qrcode-1/9.txt | 1 + test_resources/blackbox/cpp/qrcode-2/#128.png | Bin 0 -> 303 bytes test_resources/blackbox/cpp/qrcode-2/#128.txt | 1 + .../blackbox/cpp/qrcode-2/#1314.jpg | Bin 0 -> 24347 bytes .../blackbox/cpp/qrcode-2/#1314.txt | 1 + test_resources/blackbox/cpp/qrcode-2/#142.jpg | Bin 0 -> 68009 bytes test_resources/blackbox/cpp/qrcode-2/#142.txt | 1 + test_resources/blackbox/cpp/qrcode-2/#199.jpg | Bin 0 -> 6856 bytes test_resources/blackbox/cpp/qrcode-2/#199.txt | 1 + .../blackbox/cpp/qrcode-2/#199b.jpg | Bin 0 -> 3833 bytes .../blackbox/cpp/qrcode-2/#199b.txt | 1 + test_resources/blackbox/cpp/qrcode-2/#253.jpg | Bin 0 -> 9360 bytes test_resources/blackbox/cpp/qrcode-2/#253.txt | 1 + test_resources/blackbox/cpp/qrcode-2/#258.jpg | Bin 0 -> 23767 bytes test_resources/blackbox/cpp/qrcode-2/#258.txt | 1 + test_resources/blackbox/cpp/qrcode-2/1.png | Bin 0 -> 1451 bytes .../blackbox/cpp/qrcode-2/1.result.txt | 2 + test_resources/blackbox/cpp/qrcode-2/1.txt | 1 + test_resources/blackbox/cpp/qrcode-2/10.png | Bin 0 -> 1740 bytes test_resources/blackbox/cpp/qrcode-2/10.txt | 2 + test_resources/blackbox/cpp/qrcode-2/11.png | Bin 0 -> 736 bytes test_resources/blackbox/cpp/qrcode-2/11.txt | 12 ++++ test_resources/blackbox/cpp/qrcode-2/12.png | Bin 0 -> 117544 bytes test_resources/blackbox/cpp/qrcode-2/12.txt | 1 + test_resources/blackbox/cpp/qrcode-2/13.png | Bin 0 -> 101689 bytes test_resources/blackbox/cpp/qrcode-2/13.txt | 1 + test_resources/blackbox/cpp/qrcode-2/14.png | Bin 0 -> 680 bytes test_resources/blackbox/cpp/qrcode-2/14.txt | 1 + test_resources/blackbox/cpp/qrcode-2/15.png | Bin 0 -> 38087 bytes test_resources/blackbox/cpp/qrcode-2/15.txt | 1 + test_resources/blackbox/cpp/qrcode-2/16.png | Bin 0 -> 8227 bytes test_resources/blackbox/cpp/qrcode-2/16.txt | 4 ++ test_resources/blackbox/cpp/qrcode-2/17.png | Bin 0 -> 13315 bytes test_resources/blackbox/cpp/qrcode-2/17.txt | 2 + test_resources/blackbox/cpp/qrcode-2/18.png | Bin 0 -> 32840 bytes test_resources/blackbox/cpp/qrcode-2/18.txt | 2 + test_resources/blackbox/cpp/qrcode-2/19.png | Bin 0 -> 82305 bytes test_resources/blackbox/cpp/qrcode-2/19.txt | 2 + test_resources/blackbox/cpp/qrcode-2/2.png | Bin 0 -> 2327 bytes test_resources/blackbox/cpp/qrcode-2/2.txt | 1 + test_resources/blackbox/cpp/qrcode-2/20.png | Bin 0 -> 84177 bytes test_resources/blackbox/cpp/qrcode-2/20.txt | 2 + test_resources/blackbox/cpp/qrcode-2/21.png | Bin 0 -> 16219 bytes test_resources/blackbox/cpp/qrcode-2/21.txt | 2 + test_resources/blackbox/cpp/qrcode-2/22.png | Bin 0 -> 110374 bytes test_resources/blackbox/cpp/qrcode-2/22.txt | 1 + test_resources/blackbox/cpp/qrcode-2/23.png | Bin 0 -> 62686 bytes test_resources/blackbox/cpp/qrcode-2/23.txt | 1 + test_resources/blackbox/cpp/qrcode-2/24.png | Bin 0 -> 19327 bytes test_resources/blackbox/cpp/qrcode-2/24.txt | 2 + test_resources/blackbox/cpp/qrcode-2/25.png | Bin 0 -> 19175 bytes test_resources/blackbox/cpp/qrcode-2/25.txt | 1 + test_resources/blackbox/cpp/qrcode-2/26.png | Bin 0 -> 20413 bytes test_resources/blackbox/cpp/qrcode-2/26.txt | 3 + test_resources/blackbox/cpp/qrcode-2/27.png | Bin 0 -> 14399 bytes test_resources/blackbox/cpp/qrcode-2/27.txt | 2 + test_resources/blackbox/cpp/qrcode-2/28.png | Bin 0 -> 229354 bytes test_resources/blackbox/cpp/qrcode-2/28.txt | 1 + test_resources/blackbox/cpp/qrcode-2/29.png | Bin 0 -> 9362 bytes test_resources/blackbox/cpp/qrcode-2/29.txt | 3 + test_resources/blackbox/cpp/qrcode-2/30.png | Bin 0 -> 365 bytes test_resources/blackbox/cpp/qrcode-2/30.txt | 1 + test_resources/blackbox/cpp/qrcode-2/30a.png | Bin 0 -> 16914 bytes test_resources/blackbox/cpp/qrcode-2/30a.txt | 1 + test_resources/blackbox/cpp/qrcode-2/31.png | Bin 0 -> 105576 bytes test_resources/blackbox/cpp/qrcode-2/31.txt | 1 + test_resources/blackbox/cpp/qrcode-2/32.png | Bin 0 -> 707 bytes test_resources/blackbox/cpp/qrcode-2/32.txt | 12 ++++ test_resources/blackbox/cpp/qrcode-2/33.png | Bin 0 -> 310 bytes test_resources/blackbox/cpp/qrcode-2/33.txt | 1 + test_resources/blackbox/cpp/qrcode-2/34.png | Bin 0 -> 319 bytes test_resources/blackbox/cpp/qrcode-2/34.txt | 1 + test_resources/blackbox/cpp/qrcode-2/35.png | Bin 0 -> 313 bytes test_resources/blackbox/cpp/qrcode-2/35.txt | 1 + test_resources/blackbox/cpp/qrcode-2/4.png | Bin 0 -> 630937 bytes test_resources/blackbox/cpp/qrcode-2/4.txt | 1 + test_resources/blackbox/cpp/qrcode-2/5.png | Bin 0 -> 274570 bytes test_resources/blackbox/cpp/qrcode-2/5.txt | 1 + test_resources/blackbox/cpp/qrcode-2/6.png | Bin 0 -> 1278017 bytes test_resources/blackbox/cpp/qrcode-2/6.txt | 1 + test_resources/blackbox/cpp/qrcode-2/7.png | Bin 0 -> 8374 bytes test_resources/blackbox/cpp/qrcode-2/7.txt | 1 + test_resources/blackbox/cpp/qrcode-2/8.png | Bin 0 -> 6023 bytes test_resources/blackbox/cpp/qrcode-2/8.txt | 1 + test_resources/blackbox/cpp/qrcode-2/9.png | Bin 0 -> 272 bytes test_resources/blackbox/cpp/qrcode-2/9.txt | 1 + .../blackbox/cpp/qrcode-2/estimate-tilt.jpg | Bin 0 -> 3173 bytes .../blackbox/cpp/qrcode-2/estimate-tilt.txt | 1 + .../cpp/qrcode-2/fix-finderpattern-order.jpg | Bin 0 -> 11524 bytes .../cpp/qrcode-2/fix-finderpattern-order.txt | 1 + .../blackbox/cpp/qrcode-2/fix-traceline.jpg | Bin 0 -> 15215 bytes .../blackbox/cpp/qrcode-2/fix-traceline.txt | 1 + .../cpp/qrcode-2/gs1-figure-4.15.1-2.png | Bin 0 -> 289 bytes .../qrcode-2/gs1-figure-4.15.1-2.result.txt | 2 + .../cpp/qrcode-2/gs1-figure-4.15.1-2.txt | 1 + .../blackbox/cpp/qrcode-2/high-res-1.jpg | Bin 0 -> 44158 bytes .../blackbox/cpp/qrcode-2/high-res-1.txt | 2 + .../blackbox/cpp/qrcode-2/qr-inv-1.jpg | Bin 0 -> 20722 bytes .../blackbox/cpp/qrcode-2/qr-inv-1.result.txt | 1 + .../blackbox/cpp/qrcode-2/qr-inv-1.txt | 1 + .../blackbox/cpp/qrcode-2/qr-inv-2.jpg | Bin 0 -> 11110 bytes .../blackbox/cpp/qrcode-2/qr-inv-2.txt | 1 + test_resources/blackbox/cpp/qrcode-3/01.png | Bin 0 -> 43311 bytes test_resources/blackbox/cpp/qrcode-3/01.txt | 1 + test_resources/blackbox/cpp/qrcode-3/03.png | Bin 0 -> 42027 bytes test_resources/blackbox/cpp/qrcode-3/03.txt | 1 + test_resources/blackbox/cpp/qrcode-3/04.png | Bin 0 -> 47394 bytes test_resources/blackbox/cpp/qrcode-3/04.txt | 1 + test_resources/blackbox/cpp/qrcode-3/05.png | Bin 0 -> 40616 bytes test_resources/blackbox/cpp/qrcode-3/05.txt | 1 + test_resources/blackbox/cpp/qrcode-3/07.png | Bin 0 -> 43790 bytes test_resources/blackbox/cpp/qrcode-3/07.txt | 1 + test_resources/blackbox/cpp/qrcode-3/09.png | Bin 0 -> 45056 bytes test_resources/blackbox/cpp/qrcode-3/09.txt | 1 + test_resources/blackbox/cpp/qrcode-3/10.png | Bin 0 -> 25621 bytes test_resources/blackbox/cpp/qrcode-3/10.txt | 1 + test_resources/blackbox/cpp/qrcode-3/11.png | Bin 0 -> 35538 bytes test_resources/blackbox/cpp/qrcode-3/11.txt | 1 + test_resources/blackbox/cpp/qrcode-3/13.png | Bin 0 -> 17182 bytes test_resources/blackbox/cpp/qrcode-3/13.txt | 1 + test_resources/blackbox/cpp/qrcode-3/14.png | Bin 0 -> 30543 bytes test_resources/blackbox/cpp/qrcode-3/14.txt | 1 + test_resources/blackbox/cpp/qrcode-3/15.png | Bin 0 -> 30758 bytes test_resources/blackbox/cpp/qrcode-3/15.txt | 1 + test_resources/blackbox/cpp/qrcode-3/16.png | Bin 0 -> 29930 bytes test_resources/blackbox/cpp/qrcode-3/16.txt | 1 + test_resources/blackbox/cpp/qrcode-3/17.png | Bin 0 -> 40967 bytes test_resources/blackbox/cpp/qrcode-3/17.txt | 1 + test_resources/blackbox/cpp/qrcode-3/18.png | Bin 0 -> 43617 bytes test_resources/blackbox/cpp/qrcode-3/18.txt | 2 + test_resources/blackbox/cpp/qrcode-3/19.png | Bin 0 -> 39734 bytes test_resources/blackbox/cpp/qrcode-3/19.txt | 2 + test_resources/blackbox/cpp/qrcode-3/21.png | Bin 0 -> 41307 bytes test_resources/blackbox/cpp/qrcode-3/21.txt | 2 + test_resources/blackbox/cpp/qrcode-3/22.png | Bin 0 -> 48041 bytes test_resources/blackbox/cpp/qrcode-3/22.txt | 2 + test_resources/blackbox/cpp/qrcode-3/23.png | Bin 0 -> 40821 bytes test_resources/blackbox/cpp/qrcode-3/23.txt | 2 + test_resources/blackbox/cpp/qrcode-3/24.png | Bin 0 -> 42331 bytes test_resources/blackbox/cpp/qrcode-3/24.txt | 2 + test_resources/blackbox/cpp/qrcode-3/25.png | Bin 0 -> 44267 bytes test_resources/blackbox/cpp/qrcode-3/25.txt | 2 + test_resources/blackbox/cpp/qrcode-3/27.png | Bin 0 -> 26280 bytes test_resources/blackbox/cpp/qrcode-3/27.txt | 1 + test_resources/blackbox/cpp/qrcode-3/28.png | Bin 0 -> 28662 bytes test_resources/blackbox/cpp/qrcode-3/28.txt | 1 + test_resources/blackbox/cpp/qrcode-3/30.png | Bin 0 -> 29430 bytes test_resources/blackbox/cpp/qrcode-3/30.txt | 1 + test_resources/blackbox/cpp/qrcode-3/31.png | Bin 0 -> 29375 bytes test_resources/blackbox/cpp/qrcode-3/31.txt | 1 + test_resources/blackbox/cpp/qrcode-3/33.png | Bin 0 -> 29578 bytes test_resources/blackbox/cpp/qrcode-3/33.txt | 1 + test_resources/blackbox/cpp/qrcode-3/37.png | Bin 0 -> 34664 bytes test_resources/blackbox/cpp/qrcode-3/37.txt | 1 + test_resources/blackbox/cpp/qrcode-3/40.png | Bin 0 -> 36389 bytes test_resources/blackbox/cpp/qrcode-3/40.txt | 1 + test_resources/blackbox/cpp/qrcode-3/42.png | Bin 0 -> 30227 bytes test_resources/blackbox/cpp/qrcode-3/42.txt | 1 + test_resources/blackbox/cpp/qrcode-4/01.png | Bin 0 -> 40291 bytes test_resources/blackbox/cpp/qrcode-4/01.txt | 1 + test_resources/blackbox/cpp/qrcode-4/02.png | Bin 0 -> 36831 bytes test_resources/blackbox/cpp/qrcode-4/02.txt | 1 + test_resources/blackbox/cpp/qrcode-4/03.png | Bin 0 -> 33118 bytes test_resources/blackbox/cpp/qrcode-4/03.txt | 1 + test_resources/blackbox/cpp/qrcode-4/04.png | Bin 0 -> 36255 bytes test_resources/blackbox/cpp/qrcode-4/04.txt | 1 + test_resources/blackbox/cpp/qrcode-4/05.png | Bin 0 -> 35113 bytes test_resources/blackbox/cpp/qrcode-4/05.txt | 1 + test_resources/blackbox/cpp/qrcode-4/06.png | Bin 0 -> 39221 bytes test_resources/blackbox/cpp/qrcode-4/06.txt | 1 + test_resources/blackbox/cpp/qrcode-4/07.png | Bin 0 -> 37967 bytes test_resources/blackbox/cpp/qrcode-4/07.txt | 1 + test_resources/blackbox/cpp/qrcode-4/08.png | Bin 0 -> 38441 bytes test_resources/blackbox/cpp/qrcode-4/08.txt | 1 + test_resources/blackbox/cpp/qrcode-4/09.png | Bin 0 -> 42767 bytes test_resources/blackbox/cpp/qrcode-4/09.txt | 1 + test_resources/blackbox/cpp/qrcode-4/10.png | Bin 0 -> 37435 bytes test_resources/blackbox/cpp/qrcode-4/10.txt | 1 + test_resources/blackbox/cpp/qrcode-4/11.png | Bin 0 -> 37980 bytes test_resources/blackbox/cpp/qrcode-4/11.txt | 1 + test_resources/blackbox/cpp/qrcode-4/12.png | Bin 0 -> 37556 bytes test_resources/blackbox/cpp/qrcode-4/12.txt | 1 + test_resources/blackbox/cpp/qrcode-4/13.png | Bin 0 -> 36617 bytes test_resources/blackbox/cpp/qrcode-4/13.txt | 1 + test_resources/blackbox/cpp/qrcode-4/14.png | Bin 0 -> 36207 bytes test_resources/blackbox/cpp/qrcode-4/14.txt | 1 + test_resources/blackbox/cpp/qrcode-4/15.png | Bin 0 -> 37800 bytes test_resources/blackbox/cpp/qrcode-4/15.txt | 1 + test_resources/blackbox/cpp/qrcode-4/16.png | Bin 0 -> 39406 bytes test_resources/blackbox/cpp/qrcode-4/16.txt | 1 + test_resources/blackbox/cpp/qrcode-4/17.png | Bin 0 -> 39504 bytes test_resources/blackbox/cpp/qrcode-4/17.txt | 1 + test_resources/blackbox/cpp/qrcode-4/18.png | Bin 0 -> 33021 bytes test_resources/blackbox/cpp/qrcode-4/18.txt | 1 + test_resources/blackbox/cpp/qrcode-4/19.png | Bin 0 -> 31638 bytes test_resources/blackbox/cpp/qrcode-4/19.txt | 1 + test_resources/blackbox/cpp/qrcode-4/20.png | Bin 0 -> 37672 bytes test_resources/blackbox/cpp/qrcode-4/20.txt | 1 + test_resources/blackbox/cpp/qrcode-4/21.png | Bin 0 -> 34755 bytes test_resources/blackbox/cpp/qrcode-4/21.txt | 1 + test_resources/blackbox/cpp/qrcode-4/22.png | Bin 0 -> 33089 bytes test_resources/blackbox/cpp/qrcode-4/22.txt | 1 + test_resources/blackbox/cpp/qrcode-4/23.png | Bin 0 -> 33287 bytes test_resources/blackbox/cpp/qrcode-4/23.txt | 1 + test_resources/blackbox/cpp/qrcode-4/24.png | Bin 0 -> 31320 bytes test_resources/blackbox/cpp/qrcode-4/24.txt | 1 + test_resources/blackbox/cpp/qrcode-4/26.png | Bin 0 -> 36983 bytes test_resources/blackbox/cpp/qrcode-4/26.txt | 1 + test_resources/blackbox/cpp/qrcode-4/27.png | Bin 0 -> 43070 bytes test_resources/blackbox/cpp/qrcode-4/27.txt | 1 + test_resources/blackbox/cpp/qrcode-4/28.png | Bin 0 -> 36416 bytes test_resources/blackbox/cpp/qrcode-4/28.txt | 1 + test_resources/blackbox/cpp/qrcode-4/29.png | Bin 0 -> 36809 bytes test_resources/blackbox/cpp/qrcode-4/29.txt | 1 + test_resources/blackbox/cpp/qrcode-4/30.png | Bin 0 -> 36012 bytes test_resources/blackbox/cpp/qrcode-4/30.txt | 1 + test_resources/blackbox/cpp/qrcode-4/31.png | Bin 0 -> 39393 bytes test_resources/blackbox/cpp/qrcode-4/31.txt | 1 + test_resources/blackbox/cpp/qrcode-4/32.png | Bin 0 -> 42537 bytes test_resources/blackbox/cpp/qrcode-4/32.txt | 1 + test_resources/blackbox/cpp/qrcode-4/33.png | Bin 0 -> 41999 bytes test_resources/blackbox/cpp/qrcode-4/33.txt | 1 + test_resources/blackbox/cpp/qrcode-4/34.png | Bin 0 -> 32912 bytes test_resources/blackbox/cpp/qrcode-4/34.txt | 1 + test_resources/blackbox/cpp/qrcode-4/36.png | Bin 0 -> 38896 bytes test_resources/blackbox/cpp/qrcode-4/36.txt | 1 + test_resources/blackbox/cpp/qrcode-4/39.png | Bin 0 -> 41653 bytes test_resources/blackbox/cpp/qrcode-4/39.txt | 1 + test_resources/blackbox/cpp/qrcode-4/40.png | Bin 0 -> 35302 bytes test_resources/blackbox/cpp/qrcode-4/40.txt | 1 + test_resources/blackbox/cpp/qrcode-4/41.png | Bin 0 -> 35113 bytes test_resources/blackbox/cpp/qrcode-4/41.txt | 1 + test_resources/blackbox/cpp/qrcode-4/42.png | Bin 0 -> 35640 bytes test_resources/blackbox/cpp/qrcode-4/42.txt | 1 + test_resources/blackbox/cpp/qrcode-4/43.png | Bin 0 -> 31864 bytes test_resources/blackbox/cpp/qrcode-4/43.txt | 1 + test_resources/blackbox/cpp/qrcode-4/46.png | Bin 0 -> 39507 bytes test_resources/blackbox/cpp/qrcode-4/46.txt | 1 + test_resources/blackbox/cpp/qrcode-4/48.png | Bin 0 -> 35013 bytes test_resources/blackbox/cpp/qrcode-4/48.txt | 1 + test_resources/blackbox/cpp/qrcode-5/01.png | Bin 0 -> 33012 bytes test_resources/blackbox/cpp/qrcode-5/01.txt | 1 + test_resources/blackbox/cpp/qrcode-5/02.png | Bin 0 -> 34995 bytes test_resources/blackbox/cpp/qrcode-5/02.txt | 1 + test_resources/blackbox/cpp/qrcode-5/03.png | Bin 0 -> 30935 bytes test_resources/blackbox/cpp/qrcode-5/03.txt | 1 + test_resources/blackbox/cpp/qrcode-5/04.png | Bin 0 -> 39838 bytes test_resources/blackbox/cpp/qrcode-5/04.txt | 1 + test_resources/blackbox/cpp/qrcode-5/05.png | Bin 0 -> 31512 bytes test_resources/blackbox/cpp/qrcode-5/05.txt | 1 + test_resources/blackbox/cpp/qrcode-5/08.png | Bin 0 -> 34606 bytes test_resources/blackbox/cpp/qrcode-5/08.txt | 1 + test_resources/blackbox/cpp/qrcode-5/09.png | Bin 0 -> 35738 bytes test_resources/blackbox/cpp/qrcode-5/09.txt | 1 + test_resources/blackbox/cpp/qrcode-5/11.png | Bin 0 -> 31331 bytes test_resources/blackbox/cpp/qrcode-5/11.txt | 1 + test_resources/blackbox/cpp/qrcode-5/12.png | Bin 0 -> 32735 bytes test_resources/blackbox/cpp/qrcode-5/12.txt | 1 + test_resources/blackbox/cpp/qrcode-5/13.png | Bin 0 -> 30193 bytes test_resources/blackbox/cpp/qrcode-5/13.txt | 1 + test_resources/blackbox/cpp/qrcode-5/14.png | Bin 0 -> 33828 bytes test_resources/blackbox/cpp/qrcode-5/14.txt | 1 + test_resources/blackbox/cpp/qrcode-5/15.png | Bin 0 -> 34062 bytes test_resources/blackbox/cpp/qrcode-5/15.txt | 1 + test_resources/blackbox/cpp/qrcode-5/16.png | Bin 0 -> 4205 bytes test_resources/blackbox/cpp/qrcode-5/16.txt | 57 ++++++++++++++++++ test_resources/blackbox/cpp/qrcode-5/17.png | Bin 0 -> 4211 bytes test_resources/blackbox/cpp/qrcode-5/17.txt | 46 ++++++++++++++ test_resources/blackbox/cpp/qrcode-5/18.png | Bin 0 -> 4223 bytes test_resources/blackbox/cpp/qrcode-5/18.txt | 36 +++++++++++ test_resources/blackbox/cpp/qrcode-5/19.png | Bin 0 -> 4224 bytes test_resources/blackbox/cpp/qrcode-5/19.txt | 28 +++++++++ test_resources/blackbox/cpp/qrcode-6/1.png | Bin 0 -> 1006 bytes test_resources/blackbox/cpp/qrcode-6/1.txt | 1 + test_resources/blackbox/cpp/qrcode-6/10.png | Bin 0 -> 1009 bytes test_resources/blackbox/cpp/qrcode-6/10.txt | 1 + test_resources/blackbox/cpp/qrcode-6/11.png | Bin 0 -> 1009 bytes test_resources/blackbox/cpp/qrcode-6/11.txt | 1 + test_resources/blackbox/cpp/qrcode-6/12.png | Bin 0 -> 1047 bytes test_resources/blackbox/cpp/qrcode-6/12.txt | 1 + test_resources/blackbox/cpp/qrcode-6/13.png | Bin 0 -> 1049 bytes test_resources/blackbox/cpp/qrcode-6/13.txt | 1 + test_resources/blackbox/cpp/qrcode-6/14.png | Bin 0 -> 1041 bytes test_resources/blackbox/cpp/qrcode-6/14.txt | 1 + test_resources/blackbox/cpp/qrcode-6/15.png | Bin 0 -> 846602 bytes test_resources/blackbox/cpp/qrcode-6/15.txt | 1 + test_resources/blackbox/cpp/qrcode-6/2.png | Bin 0 -> 1035 bytes test_resources/blackbox/cpp/qrcode-6/2.txt | 1 + test_resources/blackbox/cpp/qrcode-6/3.png | Bin 0 -> 984 bytes test_resources/blackbox/cpp/qrcode-6/3.txt | 1 + test_resources/blackbox/cpp/qrcode-6/4.png | Bin 0 -> 985 bytes test_resources/blackbox/cpp/qrcode-6/4.txt | 1 + test_resources/blackbox/cpp/qrcode-6/5.png | Bin 0 -> 1001 bytes test_resources/blackbox/cpp/qrcode-6/5.txt | 1 + test_resources/blackbox/cpp/qrcode-6/6.png | Bin 0 -> 1315 bytes test_resources/blackbox/cpp/qrcode-6/6.txt | 1 + test_resources/blackbox/cpp/qrcode-6/7.png | Bin 0 -> 1038 bytes test_resources/blackbox/cpp/qrcode-6/7.txt | 1 + test_resources/blackbox/cpp/qrcode-6/8.png | Bin 0 -> 981 bytes test_resources/blackbox/cpp/qrcode-6/8.txt | 1 + test_resources/blackbox/cpp/qrcode-6/9.png | Bin 0 -> 1006 bytes test_resources/blackbox/cpp/qrcode-6/9.txt | 1 + .../blackbox/cpp/qrcode-7/01-01.png | Bin 0 -> 22233 bytes .../blackbox/cpp/qrcode-7/01-02.png | Bin 0 -> 22644 bytes .../blackbox/cpp/qrcode-7/01-03.png | Bin 0 -> 22146 bytes .../blackbox/cpp/qrcode-7/01-04.png | Bin 0 -> 22046 bytes .../blackbox/cpp/qrcode-7/01.result.txt | 6 ++ test_resources/blackbox/cpp/qrcode-7/01.txt | 49 +++++++++++++++ tests/cpp_qr_code_blackbox_tests.rs | 26 ++++++-- 372 files changed, 485 insertions(+), 5 deletions(-) create mode 100644 test_resources/blackbox/cpp/microqrcode-1/1.png create mode 100644 test_resources/blackbox/cpp/microqrcode-1/1.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/11.jpg create mode 100644 test_resources/blackbox/cpp/microqrcode-1/11.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/12.jpg create mode 100644 test_resources/blackbox/cpp/microqrcode-1/12.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/3.jpg create mode 100644 test_resources/blackbox/cpp/microqrcode-1/3.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/7.jpg create mode 100644 test_resources/blackbox/cpp/microqrcode-1/7.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/9.jpg create mode 100644 test_resources/blackbox/cpp/microqrcode-1/9.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M1-Numeric.png create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M1-Numeric.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M2-Alpha.png create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M2-Alpha.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M2-Numeric.png create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M2-Numeric.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M3-Binary.png create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M3-Binary.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M3-Kanji.png create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M3-Kanji.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M3-Mixed.png create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M3-Mixed.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M4-Binary.png create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M4-Binary.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M4-Mixed.png create mode 100644 test_resources/blackbox/cpp/microqrcode-1/M4-Mixed.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/MQR-needs-br-update.jpg create mode 100644 test_resources/blackbox/cpp/microqrcode-1/MQR-needs-br-update.txt create mode 100644 test_resources/blackbox/cpp/microqrcode-1/NoQuietZone.png create mode 100644 test_resources/blackbox/cpp/microqrcode-1/NoQuietZone.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/1.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/1.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/11.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/11.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/12.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/12.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/13.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/13.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/14.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/14.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/15.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/15.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/16.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/16.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/17.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/17.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/18.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/18.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/19.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/19.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/2.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/2.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/20.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/20.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/4.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/4.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/7.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/7.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/8.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/8.txt create mode 100644 test_resources/blackbox/cpp/qrcode-1/9.png create mode 100644 test_resources/blackbox/cpp/qrcode-1/9.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/#128.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/#128.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/#1314.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/#1314.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/#142.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/#142.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/#199.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/#199.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/#199b.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/#199b.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/#253.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/#253.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/#258.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/#258.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/1.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/1.result.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/1.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/10.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/10.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/11.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/11.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/12.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/12.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/13.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/13.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/14.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/14.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/15.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/15.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/16.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/16.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/17.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/17.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/18.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/18.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/19.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/19.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/2.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/2.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/20.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/20.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/21.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/21.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/22.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/22.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/23.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/23.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/24.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/24.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/25.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/25.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/26.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/26.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/27.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/27.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/28.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/28.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/29.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/29.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/30.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/30.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/30a.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/30a.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/31.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/31.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/32.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/32.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/33.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/33.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/34.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/34.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/35.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/35.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/4.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/4.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/5.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/5.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/6.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/6.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/7.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/7.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/8.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/8.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/9.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/9.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/estimate-tilt.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/estimate-tilt.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/fix-finderpattern-order.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/fix-finderpattern-order.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/fix-traceline.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/fix-traceline.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/gs1-figure-4.15.1-2.png create mode 100644 test_resources/blackbox/cpp/qrcode-2/gs1-figure-4.15.1-2.result.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/gs1-figure-4.15.1-2.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/high-res-1.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/high-res-1.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/qr-inv-1.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/qr-inv-1.result.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/qr-inv-1.txt create mode 100644 test_resources/blackbox/cpp/qrcode-2/qr-inv-2.jpg create mode 100644 test_resources/blackbox/cpp/qrcode-2/qr-inv-2.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/01.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/01.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/03.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/03.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/04.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/04.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/05.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/05.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/07.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/07.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/09.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/09.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/10.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/10.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/11.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/11.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/13.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/13.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/14.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/14.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/15.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/15.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/16.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/16.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/17.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/17.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/18.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/18.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/19.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/19.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/21.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/21.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/22.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/22.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/23.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/23.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/24.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/24.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/25.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/25.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/27.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/27.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/28.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/28.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/30.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/30.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/31.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/31.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/33.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/33.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/37.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/37.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/40.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/40.txt create mode 100644 test_resources/blackbox/cpp/qrcode-3/42.png create mode 100644 test_resources/blackbox/cpp/qrcode-3/42.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/01.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/01.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/02.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/02.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/03.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/03.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/04.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/04.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/05.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/05.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/06.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/06.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/07.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/07.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/08.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/08.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/09.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/09.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/10.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/10.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/11.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/11.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/12.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/12.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/13.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/13.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/14.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/14.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/15.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/15.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/16.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/16.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/17.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/17.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/18.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/18.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/19.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/19.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/20.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/20.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/21.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/21.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/22.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/22.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/23.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/23.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/24.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/24.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/26.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/26.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/27.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/27.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/28.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/28.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/29.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/29.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/30.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/30.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/31.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/31.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/32.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/32.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/33.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/33.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/34.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/34.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/36.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/36.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/39.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/39.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/40.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/40.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/41.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/41.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/42.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/42.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/43.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/43.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/46.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/46.txt create mode 100644 test_resources/blackbox/cpp/qrcode-4/48.png create mode 100644 test_resources/blackbox/cpp/qrcode-4/48.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/01.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/01.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/02.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/02.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/03.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/03.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/04.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/04.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/05.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/05.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/08.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/08.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/09.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/09.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/11.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/11.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/12.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/12.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/13.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/13.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/14.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/14.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/15.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/15.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/16.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/16.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/17.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/17.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/18.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/18.txt create mode 100644 test_resources/blackbox/cpp/qrcode-5/19.png create mode 100644 test_resources/blackbox/cpp/qrcode-5/19.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/1.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/1.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/10.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/10.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/11.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/11.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/12.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/12.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/13.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/13.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/14.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/14.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/15.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/15.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/2.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/2.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/3.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/3.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/4.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/4.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/5.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/5.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/6.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/6.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/7.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/7.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/8.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/8.txt create mode 100644 test_resources/blackbox/cpp/qrcode-6/9.png create mode 100644 test_resources/blackbox/cpp/qrcode-6/9.txt create mode 100644 test_resources/blackbox/cpp/qrcode-7/01-01.png create mode 100644 test_resources/blackbox/cpp/qrcode-7/01-02.png create mode 100644 test_resources/blackbox/cpp/qrcode-7/01-03.png create mode 100644 test_resources/blackbox/cpp/qrcode-7/01-04.png create mode 100644 test_resources/blackbox/cpp/qrcode-7/01.result.txt create mode 100644 test_resources/blackbox/cpp/qrcode-7/01.txt diff --git a/test_resources/blackbox/cpp/microqrcode-1/1.png b/test_resources/blackbox/cpp/microqrcode-1/1.png new file mode 100644 index 0000000000000000000000000000000000000000..35c855d0d795380bf539cd29a05ce54fbe9a16fa GIT binary patch literal 8513 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 literal 0 HcmV?d00001 diff --git a/test_resources/blackbox/cpp/microqrcode-1/1.txt b/test_resources/blackbox/cpp/microqrcode-1/1.txt new file mode 100644 index 0000000..121eab0 --- /dev/null +++ b/test_resources/blackbox/cpp/microqrcode-1/1.txt @@ -0,0 +1 @@ +Wikipedia \ No newline at end of file diff --git a/test_resources/blackbox/cpp/microqrcode-1/11.jpg b/test_resources/blackbox/cpp/microqrcode-1/11.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bfff33fbba145c21e8fdf08ec13fe20102b9d204 GIT binary patch literal 29581 zcmbq)WmH>H(=OUVp~c!DEp9D@P`r4H2QBVU+@ZKbOYz{6;Iy~}NN{g)2=49<#i4j_ z`o7<{*8O*X+&- z0U;p)5%Du(BEWMpvgZH>3M%x$z(dDOhyL(z2ms#z1vpgSzkByy)ksBN#mL#h!Z|iP zwyEhB^Z!@C-*yZVJghQoI&4e^3@j2%Y!b}Bofxk$FtD&O(GBB&8zvS8HV*Ei$9R9| zF!26+y%%C{#X|P+`RV5i-sT#<*A^jPEP)!~qo5J_@xe`aG_?|pyDVmqvJ4R}W5=ui zS>Ai$W&&(GZ7frHKmfHa;W_ipeCbfj2ki=OX6r;)M0F}fW2vLIN)eo9KWTd6(@;PU zGs?w2^9>hbeA67q7s3*qmC2EU3z;_@mFf0`vF5>YhX;%Ri!@-UHY_%JYRlubLrq^L ztacg%Mv`j2^IO3&;nd5L2$;8s8k0dpeIgLnF^BRzszl&_i_Sdsh+kZq z$Fs6qoPl~qVGM5VMN*cj>UGOhc45Bh=xNiOHngkM@=|ykk})&(xFw}scTTl^bKV(f zgrZr0)iT8cbLsv-{&gU-R_A(3&D@GFg{_p9!(-6{k`dy8gJ9c)%>6_ep>(z8O(U=F z%-l)OXfdU4H|tP4OCXpa(QbOW!#p))`UpcVUz4=)^`LT#o?}g4kVIDs zgd*O=<+uA()mx_i@${|DI(-Ky+6?=K2rDMJPbnh(j~ctzrzze%!O$#kv@OVMb41x& zn}m`o@N_Uk$sB5H@-vbntG&yd9AGnlb|?Lz0GDB=1nG_I6PkJBuR6dN3}4yh$I|wT z9TrDwdrg?g7zLST&1#|@{^+|`*v(oD^FV#6Q`7npC$=H8DSdPmL#`b&m-?H@U;+aY z4Q)MbLl?m0Xv;TPwEN|9&h5EzsXL5Up$s_Cl?q47v(@*~xvJ0(;vEc_$8%qo3_^MI z*F!9cU%c0lDrVv+v^<1qTMlaL^XchAc*N4Ha4YyrXAr>A0~0n>LGSyo)8Z)`-Ik9i zazloRY%Bw_N@Oa7BT1giJq004q*$cZQI}+HpSyoDDOpC*gybEFH^x24IYTTdPag zV)>#BiMj(&r>Q2*&7{21Ra|~-q``XNeP6cLH^2vs;k7-ylJNMYS&TTp06D2Ryi!L0 z$MZ5SL(_-D;cD}GJUqg)1$Ua$GTM83VCIx6Ebh(8ysu18T(6ryJlBe3eD~#-m@rPl zd%7?9=M_d}o0&(8*-xhC!N8#@-CP-}{lYhUdRe6lEHHjx$z>@bwanoq_u7lrcC!Y=L7NtvT_N4x4s(PR^G4E&f9-oZ z92TN%ZTdLo=Nd=~tI!}h3{otMZ2dj0!+}fVDiFP9I)2o!nQpET8+##}yBn{IZl4*m zmnG^GF;1DQYDM#P%BU5eahLivffRj z9uL40Xs)PGK8Aytpt^Fkf`J8h|E{S?^AVp3f{~!qrneB&Rfg`4glpL|!94ApW?>xe z=B1^@xm=c}-7mV7=jBMT3dO@u!-HxxKH?DIoW+q~V&E~l1ysOwQ7y28zIe7OUFO-@ zgEmY1n#qLng2JfICcxV$?~A4P2_Igc(Fg)+u-sio)RvjhMR>3*d|+K&9h$9&<^-2Pev?>Sq@`)t7=Ji0hMvbqaH zwt+#jop}huxJQlxIpUznLT9g(LzE+iGQz|1lQc!O?I#=(Xmfx)m@W>V;iniQ9@YmO z4XN{G)iHd_nsbM)c(x{0uGFQTHVd!4LO$xJ!;D-WrX!J+vo&8xA4?B!?#J~JvH(H@ z0=YjhJxMNh(mrp&BE6i=bp%Fe&i!CcLmB;!h$*SsT5K7xirP%0GIZ zgoOf}xFQdbD=8x2x%rEcV@zoEZ_4C1O&U>3v1W^9X60qqwMVX$5J5|B5PrjVQmH7B zX>Kq*Q?E(lDqC_%&Lq7r<$YUAt;C>38voir(qe%Ohgs8N*%~@t507GpwRF$ zLEhq9NC&r^i49@g9f*w~pEm)BSYE#MVCyg^6)YIAtra3U=w^rl9u0M43e_`pGnX!j}8Vl+Pb z7k{z>$XFCnaM%zg;0e;Wfa%{Rm?y5D;%el4YkV;A6^q}8zwey+W7}mQTi?tN$=ETA z#+vphRon0&e}m8n?*=n*<7{5n(}$8Oa;t>>l*oVHrk~%rZSWTZd@+ECKrHRyXuDbq zO#TQ>PU@i2b%aT16U%# z_}mvluSfO^a@fZWQ5+@b9{TYIaw}XqbO~T%fk7}m*N?XyVz10_%BzjCnj4VfqyD~c zO@{_3dg!brbH#FaN3HutbB;MmZVuR8s(#qu2NxyaYl{y=FqsjuP>Q_*Bb*Tbp- znLlv8aOm7Y;t5U7-}W6}vPk1A$sbfTEN>s~oHyOu9qLUBc%PilymN{?swZPG zQN6W^>^u`u3EVZ(Hmy!WnaDh4DQ_+>d6P5PG=him-16H0r7(GlP6vM2Dx69VOAC^7 zFYI#{5N1_8aj;+f0pVhyp7@TS=PYY~V#cPn#5TXt%X!yGN)RZ%f1zOxGZy*PAX zB)iiPpnC3<@DMs(SZP+CLv3`Q>3{Tzu-YUi7@5}s&U^XlFz3w{BAjY(cz(PGt(hk!T8P8fw(_RNL%Tp zkOUe~wc}T3?dp>zoAfEH+QBR`=)54mwJF3()~bB8XaSHg!{z*DIak{1Db267TCvBl za}nCWGsnLe--k!~s`8eiixjs-g?&h*NO3FrKReVcbjsHmm}KKKrRYFCYqpmDyms@| zx~R69xIh*PMWyoYYAgEv#jtJXcJXR>=O5^B7Rb2gaPysYOjnvf%jI^742=`8GuW&} z8L7scPTz5n9w2JaY~JqZ_sPz#n{aEi;I4)3848umDj1DUhfuo*B^bWfVrQ=E`^`^n7(Ll-Nj^T)r2Cjc@tOTswjS2 zKy_W`107?;mbJf49F3~R8hYPV2vkfWLNs)ztIH-&8hQN{BDPy%)qAdue{7!f)VWOH zzB}5j3<3jMA4iWCY$4IHNYr5l0NMM3YPaoM$}3V954b%Wg`&BiG~BVdxOrl|5T*I1 zx_BD!AjUa(U*g}?>8+CI>i_=tT+PJfT3gZk1ia#|lh}Z;IEeyDF>316%&d8k`{4U? znvVW#bt}7oQqtrXn_I^=ed7}o>bcW)cz?W!q|@s<{e9K=+_e`GcEyPxI1-advk-O4 zWaWj&!6Z%`H8gZNW0yy|lsMJKd~qXM*A$shR_~ostW+GOf9UqM-oCgU-``W7jMb&9 z1X*+b3KQqW==Syd=j9ztl^v{I&#PuS-|MDiE!Q<~_e>w`A|eMG-a6L{$;w;|rY|o4 z0KR1N>3F#r{h?<@)zH+|3j@<;<8&8{H5E#rCrQ+0?lMS`XdYrbb7K{!S#mGTA=q%$ z{1-zAKD9I5J9w@=m0w;y0i2_p)yc`Tqme5jBNw2EB&L^tlSU&+cr{x~M*nFf{Am_s zYTtCMx=ioHdh{bdLkbD z^`+x_pLa8TAzYl@x$x0SWt3q;fV%pCCU2{Oo#l?QjPd=OJ(Y{2i=~>=b9QjTFoDdx zM^P#XPo!|(m#tJw;775E8ZHWZ*pBCHG51+UQ}kN6TcN)3`(+FnSyoniBKxX-6l$ga zHqq;0D0g_0oRf2{8H&rmE*K?|ier|steITv7*Gzc)z?a2FdeJatIkMPm*SuGchdxV zu%M{n^P|)5wej?be9E9Myc#bG6ceTyg5(QVsg?a{|Dv$~r{y+*rmtg?s`tqLa}M%S zgiiwUlGh5^-IP1hjNsQX=&vf3W1{6YEyMM|+j{P1{Y2N!j68Sh+@)gK)v|(rFk0{i z_i|PyYNv63*gslN3vPx0UFru)>OyBN$`vc;+9Xbd_%Q=> zgli4uC9b}9Vr+h)^umL!o5!BWMw^WOK4O2^&SJ7c3%9(Wwu6b&5puEP(WU{_mR6Ja zB>v7S?mZ=x7rtTZs4zSIY~;(3BlVo49rtN=Vas%KKt9UFnmt`!8(y=OlkAK3Kg`x2 zodh=teXQ&w%odH3?+QsJ#Ok$0I0=BBK8rTI@)8x8bc{rt@w>T;83pXK{U{r$(K0rJJilhEu53T{VjVlXfqDrvY z!L)9EDyIRP4<4m;VuYGsEh4CR7VDH?qNA8}9fZg7qoY6tN-1?y0Pn*74;z5cxcAC4 zxSnA}-_O}5t&k0;8m@*3?D9`9_I2RAwxj-addNB(_X0}7xYSilP>|pkVzV0ML`5RW zpC4x-VYAJ9{F=R+mjVYFg;Uoy4*cU@C__2w^6^s5TIk!KqYAv}h>{nfGW1g~LbjA+ulEZ2YL^Tzj)k0cMz zBARk>uhliGAW#;ZZ$J7@(}P#N#AyAih(fbO_Q-Bm#zzShR?sKrER2lJfG2bFY+U8uK`mXSq337wLirr2 z#XYIE`HE2cnOWx%94tA?)Y${%yhH7u+8$4)Z-l4i*mIq zKssdf|LMz?pLTTAR=bSQbQ_nkCFQ?Qd#cN&W&V^fQc5#U))KjmoaaRDmm!DI^dr?j z>hWLuU-l7AnPUB;AOAx>ViBV+U(@UDGL?OC0yBLH{}!+mem$b!QbmG8P#u9!{OJDt zdeyO!D~VVtTBm4$K-bvGMufL^-_ezxp0KNSV#vfnK{PY(RMqjf88NrqkVCDI;u>H= zG}CNAbJ9@Ri>`!Y(s3(>Mpf?;zG^yI#Vuq}s%>9vrHhjA@_t-eDkmLA(IRx>2p{J$ zspEK@-Ya~ADE*9=5$!SkvZPLtpUQQ_`Is|zoF(d)nhW2wL3zm{PuACh*H&BSN8`G= zbY-XdbEi$3^-1r>f+HkF20fa~C*6j89+hKk)7vn;?gm^omru-@eXr9QB(&t! zXxHQlX{lhZ;ZND&nBtM7cwxp6W#VKdss}F+da~p!DT4aco4*}0ZAEQcY7-Mw!}Vc+ zjT^8(46T{$(JPrVbAu~>bj3hvYx4|KwG?vLD9uKXm1cb%z|JbKr-`%@2?W-2zH*R!T_EJI zlDeHS?e@D$FJZxmR(P;tM5Ed7cK3Tfwz2xwbs>*W{uLpN{>h#2^SH|Y<7~fQR!oae?w_))QKTut= z6^c2EsEg(-_8~(D_py0LckZ*RE9d(coV1UMm#bb6x$>PicnJ)HTD&u>7kSLjt^`l+ zU%Yzo*j+9G^So(YQsNRA+Z%vx5-K-U;*YDW#E8O%FXR{Kvvey8XmN(v34g1MMg2?Jj|n&d9V5oOa6J&rUY1N zN_WT`^<#R%`0e}WErNVJmRbTi;E3T5^CKe1l_Uz|l#5#pl}-2=18??c&J-}DIThCq zT_=#BNiyK7uMalrHSuE^SNK^-NMr51PIu$CUJQnqjG_8ozHy&Q3?e#Ao*d-}AtQ_3 zX`N~e?B2*AiS|RwBTxOQ>toGP4eahjxuDl}IxYTqOV`f9Ir9!g|IcNI{cqXbs_lm3{Ks4GaQ?o^6nh0-CMNh&I*i9zV#p(PWM=}g&P`qS#cQJ z?j82_tHf>^SLzH2ZWk))yW1{m0~I&NeI_T1@x`=5TN@e7-n9rcX=}puv%I0Ro*uOd ziR;%y#mrFy+d{k38b}@%*NlNJ68|=>O{f^aY{xP$fz+Uzo>4I#y6QA{(V5ND#eJk; z@iw(bhP2P_wdE20xfoLA@!%BW*pvE*h%pn8!e>giUZ2*F=lT~kN%sKvJoj@a*sOmI@c5e-Z{Za@PxHiR`%IZ<9B#xV?7xk~EWw^- z5!`dL1V`&8G2~!8Dk9Re0`B!OG11XMFl@hEbdU=4*pSi#=mB(~BKC20t|-5@;*szw zK^%+HVVKgR9bJD-7WTo@);dityCj?+!y$*9rAA(r7h{S?!zTe14mLdOS>Wf$^`Ucu zgUi#CQzAopK|k^0oyNBbpZXKk&emf;wzlPGc-%Q!)eX$RKjiVJj~*SVX~7>ieMQ~}Ihe{>!4 ziS{S5Y&7t5bTTZ_MUhH>jS7+&7&nSFc33n{-xU2JnGK9jwtOE4y1fz5+jWApSA51A zSbvuq&rG^^RH)oo%F+bhye_%H_@T-o1TN+)Rp(yRcWat*-M$_b3rcze91>!la;P5S zsPk~&wRGJ$&fNh3^A%V{_j&R-iX*fRTV7=N4Sl@9l?QZY)Q!2eFcha|YQlYhCth?n zPp~EmMa~;~;#cfOQ7a&A%fS;&3meHO=zar6H5^KEx7A?@gTJpEF8Gs;Rqtee{Co2Y`uJ!}McNg1IY#nNRBY z+q7{+eZo9J43oUmFxbaA_5xnq%r%< zogrQJ2l_$XiQs&wcTcElgOK6JX^3A%=9wSgf5t~dBiBI2?65&Am(9gHKnS}d>o3!f zDlYktFJ)14_BOmwfQO)k7EJZZkl>1ZX!Eqlc#HuB+cIc5o3U@hMZtzP51o>)4%p!# z?8nv0Y0! z%xI>aVzS`x*BdGIwS&&(O!_g-6A`7tUMj=vFwS2AkCP%CrC}fz&L^8!hYWWFVUFyi zLB7e>@6KYtFwS+5uyYm3OR*Q3DA-zq-=TrMQ#^yYk+t-lFr)(a4(HT>9N-D!hQ9#W zITuo+VExyu34Na{K6SE}zerziaJe>SkB*K`;5YRID8y`@wQ3LA1IIzeLD#I=zmreL zUBPD3eIsgYqe)`8s0ax23J7-v^kk{%iny5_ZBTc2x12deN~*J$HfK)&0gSAFvlDgf zN6^~@8dRLz66IbtEg2W|whZn>!N|z^%$tP)EATzUF(=@ToT6JKm!6A@d^7V7ccm~H z1?$6W^<3RdWE)qP-1wbfYJoxf>3)K(%UkbFk~TQj3kO{K)Yf^nwxqg_ebzvgYw z;Bu!qS(s+gHO$KbdU;h3L;ZA$+(essM@~!XK$4Og_+<0$MVd-)b#pY?9Z+LW967XA zv0~+{f6QgF_dvEY*@l0vm(#BChgLFsO?Je19q{H8oZIkye@XP_lf8(SD@KUz7Eu`ul4nKSe1hd3Aaosang>IGHB;pQoTocLik zTcWeAiQH-}@X0l#s`M{Lr(wE!*+>)DgViOur7T-JO z!#D6BWKEH`>>Jib(y+MrJcMaCy2J+n>eRZBn06r%rn5Rp@)H`lD3?L68s-9^-Z;0GyRcB+TNWq>5o+226S+?U1Qwj!>HMbx$} zo~A$dDzEEHM+I{E5XeI5KcT!9IhPS28KpgI%tLVP1B8jB0#T(Q*^Ed})wlopuk?42_cIYzgY@kh}&prQ7TH zLS8h!KT$B8lwRNs46C$m3n+W$US%nm!UHLQ?fem84g zpA+`8jyxzlVgBnT5sfYP~ryvmYUuk_eT_-WlQC+m)mW;ot$zk(Ac3G*-NJ0>aU0?}))15Y%sr3eUu7wp(Q(pv%l_O=XXjLc{#OM6 z$8Y}(c$m>;7xI{7Bpb)`I1cHJh+i-gLB@vm?`Jj)$z|M|lx)-qR}uSyr5xw=F(Xj!FO zHhJ*QX@C4)$`h^EskdYxKAzN>8DN9|5fMR}`}$6WS#akB6# zc>YEM+Bald8M-L-^z=WXe@u5sfzhIiAw6ab634TKdbyW&XrMvgV`mTfo4&gzzQ_8Z zf-0;@LRZ!iqE%-7DJ@?Q>AlETIxAFWDIFiLT*^P>UJ)Oga(j#XSj}O&9E^SKOISzT z{P-6{-e1(pXe{wjg%qUfu(~e*Ak%M{?j;T1ZI4uq?`?gJf zd`3@N{P4qz-LEj6{%4ggFrHNZ=~lP;GBfN0p{xPa|DD6y+P&s)P9_-vYB8zYJl#v} zbBmjh#8q96ZZSWSJ;ri5Ux*2jxs-2sgupM;P63Kqp;%K!l8GJq<8s_pe(mkYzX`pW z)TDBIOG&I|#YbU#ds

a6<&CXNb|44T>-vL=q=8sC_%IFHEqSG*xM{lX$#r;&Wl_ zi_w-c3>$>?osrX@^^tRSxEVCK#=Be_dc&kgi^+}H*=`ap3q)WsgWyzh~g7 z+2&-7y(ZJ3+L>jsce1nln^xH~ssbAS->(NRj_q|2CJ~$ry-40%MZajSQ(!aX-!Iw& z7xDWAWrbqO383adRqB)o!uU6? zGYe7n_DU(xi*i{W%m--pQ`CtoAJs@+Y9QuT>I9_mI8}YpGVi{43yCm$^w9W->&ERb zhBJAi(OUZJo^<<=aOMF6qzfX@+wX$=4@IA(M;C2?L8=;P+W78;@f3TE{BMr<+2OnD z0L}nJL}UPnpxttzM| z>%eAuL!{BSc-Jc0iMGkkja-j8g(ZK6pxYV09!LjgYuJF-9qnznU)Nkc-cfjjEQtci zv(tpuFSkDa zme+~WsN5q}GS2?lbD8m(r|_{7N{x|2HL7{?w15NZ#KJ8M>9BBX>eC zh}Kn0&TD1iy!m{!Nsf@sfOR6{a!rAL)Cw@>M388`9s93SUf22J+5U;`oBAe~zZmZW z;S`{rEdE&E<<|wn$J-CNw**%%v{?@_;t2ZS_QuhYdD%~a=;yhH%tTa83Fnk)lkihW zF;z0zBv}pJ=9f}D=~jh!$(PYTdzxZ%^0y+--ZKBOf{XwBp3!<4bz85R(q7M3t(fb!;q5r-j zR?}8mb0ZJ!{6EYWN3JzSt^J902wZLb8F$zj=1`KUJ zc4!t;EOyy@SPB*JXM#0?AP~0k5FL3k?I;+}wX9{&RN-x{XFV+PM%Z|h)1g6s&13Qv zHM7Fb-kY=5MC{?mVfO^QwZLeSQ);NnAZb6te7kjz?0%|gy9!7m(#k`+KHl>(xFOMa zlj<)<1Su1d)2_{H|2b+Howd9toUC ztB(=Q4l21H$&P=Hsp@Ei7|lr~dB4s-7RoIAS{fbAr)(=^toF|HTs*hI_3(V*FUB*} zef!(23jVklBz5`L{#~FPu^1hVGi-yvtjPkr0-LuLPmkL(Y{mnZ~94k^0 zPka01^URL<&G)-x`WDaWn(DiWTvMH5)Figb#0)~YrOsPDZIDwlkuI^#dQ*2O6#8Ci zz+^PeTOSc6gF&_A&)6WHCVOK_tjrU}9aq`ILE;@XLTe11tIPKi*Z&sDA#Lx$!*C21 zk9;n)z|2mB(@vxGg(oFiW8yXD8oU(8nXai$_242nH%v@&qkJZHo+9naUW8d*t2qi+ zce-7*SiqaFRZbt64cxdPJ?*pSXl5~+o&pg7`hIlQgsaT1QY+S7`*OD1NXc8hn^azs z@ZaW}D8!Rqmap&1>}|DD(66?gM#nk~eX9U!~ezVRv@ z(%6~Ec(IY&d(dpe`r=8;9fu{nzgxKaWCD@=dd%OOojN_Ti%)&GLV3>Jh3zs^a}KH# zTRb5)@{1GwAWq9e{iV~)nWsX6O=Gi4n6_%)#8BIaWGlHbv3vFP+6#PPjesMb>dIv2 zB(o>3sdt! zO``Um8x;D%4gj>5zNG@{6qL0x3;4K51*J zesrzGeXWB;AUHy~mE45u#e&W9y`eI(G1_d*b`*T}l2>AHz!KbvehCf1Jtik= z6ZUmm?Jl59Qa?E59wa@M6Ph}{LXaO2p$-(avw;O_?YYG1NYK0stVIeuorKe)yjT&k9 zLwm@U(l&I|2-DW!2lRMHg)MevgG;B;5MZ`z9GNtIAfm{*huRR477y$_D!z_{qw&OF z<%F@V@G7q4!+oBh>>xSoAccDY@`@nzqb9>Tb6@V$Z|y4=e++88t4Po<5LPoPCpjrV zdw=)yv73A<{?)g?^NFp5f(I+i>6Pq&G3!#PIB1B_vZ>+~- z;M%8bCVQU{Z=)@$R4&CcE?tbO{-ynOUtPeqTX#`fELi{uoW1@wiiM4$(eQvw{<3O_ zrsT@JyWBc9-ul{n)SMaZlba$5@~_h~@g{ik=bMZKhzPBc2Zn_&xo)YU0r8}aT5AX< z&EN}{xNKPl-nBoG*H4ng+goLwWsezmCIT^mqMVHNVetzcz4^*iwdf4cgbb1_DDAjz za!G3~yo9_?VdH6EHBrthUrTqOP#(*TDi!*yrp#U(bx;`51yu31WBPr}d?$-DcSsA; zPXTI=f8HyP7Wr*P8{s9wTT*1}`NpK^vQsS4@EyvI@fv{X1{-)#ogLkFXxmVQ8@~uo zCe#Q-oh|CR%%_IsOC=pvaKKD06QT^Rl$zL=8ub zZ{{=Px}Xvs(}TjRb`Y{b)gv=A7&_ZOpHTF%p*nW+81!^uHxxT7EDUUG2Yz5iP&Fmf~U zib@Ap`=zXUNyF?xpvZ6XKkR=oNLy@CSbOc_x;dA1>d4YH#PbpnDf{uw{#nt;fV=?8 z*2fLcvT2{DK$~iHr2OM8^8Ie!J3C~$zESNk_GWfKKN|e|7r-;^YZLG%>DR|PW7!|nYX^U5!e6=r zKugtBR|MqJ7nP-I&~aJTT1|e=-l92!$YdXGgYhlt3WldV@-0WEBr!MhR=-{tgDhAt z;_l^iMWNPh^jv2^oyv-YVX(Q2O#YUwWLvz=6EO=IH7XH~cflNE4cY3zgAqUiDJtic+R4d!5fJ-*;tuHPGB!D!hdID&$YtbOrsNquyk; z!GkdQx)mC#YQqVgnPA+YUj4apDJSrm`K>9f>C-#!7sBc!9BHvXll2lklja#2fDt6*Lkpo|1cYhl zIw%;L>#{_1UBlLe;c%77bn#TJ+EMyB=YVFaE0XTj%|8)--cMAaV=Alb`ZML|lvWie zP2n?X+UDY4`Ym?J_UeVOZT3vTa!)*Hwo_b-OIk62tq`vHun;Dw;4$lRU1$n~kdbjF zpZTe=(%4rCZu*rQv{eH)WR&b>$-`rpkkp0YDp{~w+ClBjKo`+c@R72dVScE;d7Ft@ zWyMhrP?<(wD{Mg1rmc=PB`;g!e<|XIqK2Z0e{|iF>qt$FnOcK52y>J_pUpkepGJ0aJ6VfZ)Y3y=G zz|Z|J23Qh5v3hMtbgRAdHOWPGG&(gQFXbY@NtG;8wT=un|hd7DtS9W+~$ah9fgxlG#C3x6@}7 zEkY$Ex4^`n8}_|EJZkJRV~w*=pg!LUJ(mJ=LLYlMIXeH;-!`?2Cm|(1wa}1sVF0LW zvl8IXK!}kUcnGJ&4OZtPizDs}NIAxj$5!7IQ zOpSW#1rFE8PlH6jK)`Z-0OrEa>)zAPc;;9Og(s z)KOWr1Hw@>?aRdN+kAJ*0%v#$0BHAj>6|h!02l1y)u`o~wwWDGy`{HJauvuDp3mfk zFd6a7`sB21?4v7b$-Oh)GYCt=NX1wl6E0qZ`sFJ9m<0lku0)PrU0_Oty;gLjeJrm# zaVN7&nfFs0b0+CS?fpidK&h#_5RNQr44;js>C{s67CGlqO9W6u2pi)&x}IdH<^zt)f?LuSo;rh6 zSb%9{L!HyA7R)BxU1cb;ScWpU{!M=RQtrJ@7n@}590!JsVAM>*>qU}t!|9?wZmnp$#egedN?6n5 zCYj}G5E)snr5)x=yB42Vl%BueMPt3H~<7I>u$8!DaWEnJ4I9A;1!$IQ1sd5+OHX zJU=&B_WPIpCCV?YzQO#i6Knuu!0P*1Gx`ed0vFB^fFK@8B{>a*$1}i%fA|0)490>k z7K2*Myn<;d5CBsZ8a#{C_|gWg(8Z=m)6}vFgEpj2t<7ey9qVueU})hf-Xfqf{+)p~ zgyGD#t2y}8cGE{|D_jl&)K?f%ZO}rm35IyI1kwb)7?rQ|^f_|YbuUn9A&jc%A_ZSs z*T_Bm4y~qVDB5^iK5`>cj$bC7wct$?cJlgAB%)T; z?3I*`ry``mE-GQHH@vd*@LdFqO&a|49XA;nMckQ17|D<0E_@P-k?9cA1y8gaB~sDF z2ei$aPc3tGizbY}mD?Ne4_ZuvLT5$KyxmDAX5*P6gOyX=$lp*usw#?W;1>QYGSkhq z6&@~0Nq;MfhBAv;50=iEbWmq!7Ytp=e?$E5Wj3sT4gLQxZ1j6=m>4AVT*Mz#P3WAz z#=rf6_%-@|_5Z5Y6FVDim1Y->FE`cB*~MMhIB7Lr{qeY0DE0E#*oXr4lm78oas(}s zw7*^KQ&Q6U>=j&B&VsY7)?xDJ%-QFecxr$Vx3QdiaEuXrOXyJOB-SZvyHpUlCxn!d zBgR9B9q^pQh6XDiw-J_`JTl~{*InCM#to@-ed~}__Mw~D**ct9$GK~0ce2uJhPtDH z^zl3?`AfmIfg8tzFjMDuCKKNV+G1UK78}IH0e2F3{wEGste^S$e-ei;g*7;bHCfIE z1em|PoHxQ-U0{4xiwe}G!(8_cj|rUkblB}G{530AK0be)(>c?Cd@&8)EHRWjn+cuw zsjK(IGo!4o|NfP?-rry&UFhpi!*b(~#w<)i9fy;CuR7fcuNJ?3LWzOS80hH2`FlT6 z0HgOF=@8jEi82h<-PbGdx2 zN4`6i?bi8akzkGudczDg)VD}qt$3FznCim%)ntvUdB#fQr)a;k6U{StN7lv@Bi5X| z0WNcL*OUOv5rwdneGUhQ&}lQ}gU3a_9D{YCON{>Woo-Rizz2u53nIWg5%JTI%tOi8 z-|N!3>q$fP-K)>U9R7Tkr+rDm@94LP2z{9c&ca@hC3O*o0FjUFT@JUqm7p@Yc7Ebm zbX#~kttJEG4YY5$rr$N`H$*o;|Lk>(%nS3Z8L<|6l95sM?p)y14@CE(ye#Sxs9QSX ze^s?V@}FCGsBBkogBi2~j9pVyNEJh=Aw&@IkV-@81iH^QzAJw&TtrT?2$__YvExfI z>%?+^xAj4f2#@KaYsc5Ho@RSkwRPIK@L&0T>xTPv*Eu-c)-HL)2rd)KJG6O%Gal32 zJ!*O!P_PpEGPpjrX0lmEGfRR$y$ew;`_0P=G^b2VFS z$jI*WGC)C37AudH=X?Bq{=sbk+|Skc&#ue}w2_kyi@hBD+z5M%s&}id%lbV9g1hpl ze9Z3JweKj^h`*J)m(n2#{Vlv$K2PlTNcXdo#k&Xwf(2S1#lU7zf?Q|p#`d`Q^k|Ig z3cblO12;zvDkH3jTZ9vn!fR^@cbu!R7_&ODR2lYl})05l3xY!>gKOYR1e)7)RYpY)4_vmp0kXIRIB5dK7}f zV*|1BOgipTCo`jUsKSl5#YBz#()+|H6rMKyAx;(J`}zgAV3=5LRkdQ*{+pXVPb}?A-UXU~caB zH|W_0-;=%-T{AL=>rA=0d7F&B8S|~(#Ai>RF(AXHza50Xmz_T*pZ-dN1#AgWNiufA z@qF~5Rzbj_Nq-Ptj_L`4#ez^sRKf#xc{&X_hmdFbmn{mf(3h?due$D|=!g!J+m_zF zWL!sA&9V(e>~f2QxKqAzT^>lAdSFi`azJ?EXSa0eB3r!g1*LhWMhj75wH zWfM`8zDAwogZ?kVm$t$>w2~%D0jHF!;+>w^qk((%%54QyTz_K!VjyWp0tIS+-4d;d z=1kXQm(Cr#7yzve9!8a`{n-S)K8$$KetaG>ovE0F=ArZ{h$iFbTDKIYqibm&ul(7x z__N2m9pv}u7Y^P3>FLemp?d%KaYaU12aSEkntC+|MHnVK5lOEQvQydju}+p4S;kU> z$W$uzB5TIj_pPyHDaO9bHfC%y7<}jb`8^)L^WQm-bDrlu+jZaPdS1`#&Kdk%A2zj% znfyFV+!5@=IB%yfuyk2m5D~^rrw)AIX#Mu5+XAWU#Nghqzt4Jq#)L5}qzX*P>^U7E zlg^l4u!+^|>4ynyRdUN6`W`LjtZ^@>x6@_kkq>;Cj}q0Bj)=6Xch|XGZ84ZLB#k4QKMOP-@f^BpNz?uvK4Vg>7=pL`MSf-z6f;b} z6V3BZMO!8lp*htf)tavDhS3J(qj^`_8MR1m>3zC+x6z96ulTZ)rJV2yliu0MZtb>u z>IIJIPo|Yp-^OZ-@?54sTAp$Ba21D8qRWc0q62SU^SO%^6Fcnb=9Qe*was@D1pSW~ za5k(62~_70SK`RO3mF!To5DcO20kUz+H11QUkm8BGz4XT* zwYroD=qkG18`sw^M|Ng|DfR4Pz-rZpzRMJn7ulZZma$7R47Wbl7AEKk$mAAy6yffJ zmShC&kzhaFRi2}Uw_d;mE|S^ugLW|cu)iP8eGP@~FiiBUbC1+4>i>oAAS2-CIwu_F z4(bu4$*3Z7e}S~v&#h3xJT#UK8rSQQLO-t5z|-UQWr$MW5YZ$SJ4tz}cw>WdP)9Ti zLaIHdZ*Xojni Q;n$p1DB5rA^VOT7HkpRh)OO2?6UV4Hhd0B)BRnMsklieGOVL! zc&nhOXuG8YvF#_H(m+r(jh048S{WSV?#TOO(10`uBZU8F$&t~DeiMDuiZsXz@|LEmyGN@@uHl?6ByUa& zu(UTQF(0QF@7s@-dR&$iEfeXm0lo+~=cpt|)0GFJjd3=?0y7W&^G0FZvHMA5?ek+` zBQgS}VSbN@<@V(oGa`q{k;7&=ZYOq-Pq`BYA5{*2F!o@8nr3w_(WViWA>b@EkMrn4zzW!s|Lljl`y#>1zZegU9L=HjWR`hsl~^Uv zx*{44!A?Am{AnwP!(b-eIh?Xq?cuAD|9BzrIaV#EOgLlmqG zuJ~+^E$HcmnKdU@dXUrN5mID0+%v^)+I*16<-A{s{f8;4(`Qt2v^d~UKisQ!aoxnd44{B7U*SI35}p_&wId0aPk%>h za%ipNPQRp8s1#wNB(*|-4hkbOAC@IQvZEnGnnLMyy1Hv;OpfP{V4B^&u~Oa6>UCQ8 zh(~mD7Q^f!%AChwSFlv?QUd(SR!ChKl64bt?|##NyDlp(DJ#fJ&mjTPuC`T~m;%Tw zhJXY|!ZCo=(?5~Sc(R`Uc%$80ge5Gmeu<2bq%MPD9H$q5p;-)LrFNX=@H>Y{=Fy+r zC9=_OrfQ*Z>2DO<6Ng)+$VUy9yYAin=seEmc_go-)E-(WZLt>^0-a;DYx)q#5f*O$ z1O;K2g#x;HRu-wb0T%X4p&>%_8LD;_d<==Z+ z5ONcwJ?Uh=dO`z5*0W#z`1erNgCZ#jKTkqvS=N%#Eo8$ivQtNBsMPK}m>$}EC&au1 ztDgktjfO@Vr5{xgl*vXpvn1Pm ziz+an5BI;r$95;ZslKU{%B7vR8!g&gzAq!1z8uu#;{+X0&LnfI=B!Y991ysGugjVr zR-Vm@gv0<3gT0?`2f>=-G!geC&X~lxXWuarp0r8ts$AJ(2QDpck@!xP&UEePSb?g2 z#SurU%L13ABqNw1b?MY10)^TMNNH0FJOpG)?f7go{-X1>*?5)#0{rPV+pF|R-`5L0 zrpEF}Adpo%aQT-+@=?wjnnll^@Ofy*i2A>)M2nZugw*oGBqd>DSY+1RRuCM}-gA$b z01JSZ^^uW@(5%)JBN_op7LPsUH&pGqv(SS|uGM6Gi>DyN7Yi`pT!YssTVQbdtWXyX zrWYEXuqKzTsxq=6Lx@Od4Z_JbYc&M;SkLc^`fIjh=^d*Jz1@!x(q1cUqeU^mKn7$H znW~c1Dh!|t= zt$+eWO+-QZav)lUAU|1rlu@ItVxu^v6hesz1J@`Kw~i>AhE|{QmD)jVgIwnNYl#B5 z0|&kh@*ySukZ+b|zTjCWPQBV+LL6D@VP;lU56=`_qY1RADW;JpAGCWD9Uf4 z1%?kSV^3_PtI1REo2*o(Y>OkvR0Z2Y=~^{Eh^f6)vEy0?457LQ4dzCpNSo7>b=NPii?A-JxtndrY|W2_ zo|bL{KFpml-SwxG&#dMiCl&ut;*{qrJj&UxpO#OZY_C}SEjFFZIPVN8+}!F%0HME0 zgMvXshPEC;=HUSH#Wx329X?R)(Y1^tBCGv|qipEu$H^~tHDS}>^SyySRjVGxTUs`* zo7l4Yq?N4`d#crXI2b%QT5Ip!dqsN;)NY@(xEh4C6q#)=hRz6h4r_kqw?Wv1*pZgj zCwi>d8ABRuMiDKPuHPgCC69%4rt#o9@^$x-Y&S}D-wbc<%Q*fNT%mcQ>bT5t#Wj+$Q!w;EVXm>FeMaCW_Vf9gIA1d8?Mu z{4|EyHzuL_CD+LvX%8s(y`s)9k9G7MWA@L_X1FZH>|WfBV&7CKKV4l;pn5ks^pi!7 zWSe{Fs&v3o8<>ArUWE|QCU3~^xlwNm6~Fm=_UqbA&AS&OEp^CQwF?7WCnguH*E-9& z_YB~Z@gM(zf3s`uu>bUdz3flT)oc4D^2iEtfzQvPbHv><2yLdU>`k4CWiiG@cmkP69}!Gn`N;qati z(34F&@M1fy3BpkabBpn?r9tShTo`cNJFBL(Z#D;n+;Z=;s)=orEhiS#z-;WB=E$LaH z@d_@QT&fR1dPizvz^|pp$ON5>sPx$P&7ba_W^{s->kSOuk+Q^5^!6+QsoH&_GQJDR zpkWgX=Q|68;xU2OZ|BJ6pJc&V4Pyi&1CUd!KD$1DHnvAC_rntp3o6zMHgidq1jT2# zw>@GM*~z>EEPF$5^ZCw^_-kLxM6<>BH$dWIv-{rf)-B(-2JHcTK?IhYq|wV|Ipq|J znou%W+z}x62~wW|ZcsX7I))bh#@fKuPUlxQsDUcI=G==-LUzs0OQjf+U<-Nyi9*Vv zIogcoYah3OWmGjI?;tYHn0}`h%+$Y1hlz?j)zFd_GSHly&Y(JyMExU8ZYK`+geu<$ z+@VCaA1)XMtt@9Ys8RvZ4d4H~`SWbXax8T@IgGAe-bK@>h^VOQx^%_+jA^m<)@peA zHj>^$M}FJ$nV+XLCkM*l>4n(FfZ!<-tScY80O!QPuej!BfP2Ht45SvE8wX}f;FTg@ z`+U@*rtDSO*XM@_Tzp%iQ<+N({v^ANV$ICd+TgCu5$@@n!-1ka8>xEEb3-r$RmpQW zVSjN-Q5*2aNfW(3&A1*xv3NO&7ZG2u_PxxR%2)2LSFSwVS9@LXJ!P1E75<0F(Gv>R?0E3X_3}8AWIOdl0ms0KEWZWkjYD#}^^IKuSo++0>&kS(EIH$_Il+1&jaWnk%!3?A^WB~0|0Yqyx<&5Y8m*Ba){u64wlMBqP^$2q( zpjTbist;Zqr{}V+LAoU5mwu{{;QK6{Cc>emyxuFo1G}>ZHtz^+M#g=8XST_;q$6+F&K-x_=Y*u1n=GVpQq1G)NVtOR4*tB42c0bvd8jG& z7N0wMx_+pQVceeCm2ADsa*&(4sjZ*a4^@v}FGHrnVQuD}o*xX$dNtRyj-z7M}#_|3e2qSE42 z<`6?uqnkBzB&F-79XYM#iJll1AgXagf3^pVAzLu!^%icU!+OCqq_0ZQ-^9e*v@}(@ zn1|6{y!N%2)T?*bua-sjb`C^KG>@|`N%VLZtY~aV9!`&tOoG1{J@z{p4{N0k=n4m- zo6eX}xonL!1<}j6$%sfdQ>o#b7itLfe^1VsI0;$uxw-tJOh4b|jI&(h5T6=DAD_S2 zvxoVP|K3y%-kDiOqL6H&|Gk- z;v%*>VbZ0hHOxk+q3>;|W8a)kLF=`7AZ>^~_^`H;8fW^^;-m`kSc)0ReG9RbSm zH->#Cxms^~L~96Gt$X{e*21H-eJ%g4sOKK{1o*RR8Yix=9>G**8N7jMd-l2{yHpDe zta{!1&Kc9WMI>{JHVZ61!E@{{wn>rA-J^p>vODnHuMp+YMK4@-m^?VDs5@gKoN5Po zl=4cHA<}PV208G~JNEvW?yK5d(aCQz9^Rbw;7MxKJR%PA^f1@&sYH3j&z%@u-E{Ae zh!2Cg5ucq{f6uk(xG(4=hRdYbc&cCAvV0z|z@9&F{$KI0Tt#Ik3BA4CYTG4^w5@;> zmUc5G*2Tn%hMoXC>ZrNDIo2G7M`p-{bFY}X?>{HHVdLhLtzwwx0^KrgI;af8E@kqh zbC0B?BrnTTi1e(5IaSjp$SB1g_(GE*NmgY|B{-O1L5r{GnZd}UMChzFubJ*q@|zFa zfMQ7AAKX&F;8v*aaUg^~dd5Ugoq(*OOxI>}M1PGb`_ff1vQO@10|lZzHb*CKu3lfo zZj*t&{%ygXO-o&MQ)^0fIc-dDSa`q4>sSYTui}EZWO;nrV7t^Qx)ki>q20>w{=$O) zV$1Tl)_dy^6W-G|IH^r!u~tB{`z-MElbF&-rAEL)bP1i9S5LkEoiLpQ)bfkv<~d_R zi^p;~#%ptZYT4tMfrJQ<&X__j$eSO_JCv+ghg7TLx>U?!nxxVsWCAaS~-xGk$0;sV^vrOV|7b2dL10O>**XGvl*ywD18K;%o z(OHH!>u5EzkZJ1Eg$w3WKWJg)IOVpR$?!>Ik4)N z(@G?^$=SeHez`jMj0vk8S?Mdg8z2=eU%D6@_F%Q>82@8F)^BnntiJ}ZJJ^{IY}Q;( zUz+*;!2boKt2n)!9ym_BkZsa1-2V{bbXi*b$0bL716zY7rz@IS@08N+u7BMj#43)U zyaW|18WL~ZDbr;>dOZvHB;?tK`a-mOFJQ}wyrC|h0JeSe1`?7-I`2Ng>hWb&rUNn` zsRH*qeECy6$%8WKT|*|p+|sq#v@4jP6Xp1P^n3Bug4tsK0;z)K?q^ z;kUX$eSQ-DLN_ffs;KirHWQyxykr?O&yRPvpqts93{-p%#kA>>ym5ZgB+C3WqD<=e2&^8G7JcMTrLp*!? zZgt)I)^Y3HcA15wBQ?d5f{gbIiUuZTb4U4KCA$oYhN}M^%#o-%XcLgOEwv_Dh8+)b z`9$f0?$ukwfHNQ~e-=td8g=ZwX5vF1=<&Hhqu)nbefarj^c=XWzk9zF`3K2|LNt$< z2XY>r`sA7UocPcAc^-)$o-r{mo;$JCw2g zK#Ja9$qzS}ut4FQw$Ec^kLs{dL`H`i9$59eqF15lxJeY57t_@=302oD$hmp$2)|P5 z-)9uMSR%HcmE9{*7h-?u0X@dz!tBqLcNRP=x}8m0y4-3+GNRV8+hf_|ILpbYdK;@Cm?S zyXa!Ig0aZ}V#c$dXTPK8o&0i!UeC&YRVgS@aK; z3awuVq(Ul%5e5ErQowL^NbQRtEXi=(-7_*q`X}p+ zOHsK(SGatv6A%Z6Np9nDsOh!9u)_X(d%@gnnOou86RD_lqmXUoOeK%qZHN8ml(Zey z0a=ZX8lwn~ERN7suV0#7xLqO^iPyt%itbn(ZTilry<&LhQlT#WM^_d zQb0Oz>>74-RdGN0rc(oX|3VR#l~8!t7w1CZyqQ+Bv>@+Xw1sdF;SDQ;mEJ%mK!xx=Ytjo4b?~1$4yeb;-(B7Y zJJhj9{)*^Vtf)fsin!!t%+7vqVGu5S706mfx` zJ89c5oSUD5g{lAxOla996^itM^%+zD`4N6vY0ZRWQ0N{}xEbd@h<=c69@V9mINf)k zV>dl3`)&MB-Ua;NH)k4pMTlP?-(7!@2MhO_gaH;>o7iCrb7~~(u1KGBahn;+%0g48 zcXoPJrdInZ7CLn5E7l7i$=#NY`JU zBLkIvmlY0n9KmC9Dfw;Xu@9ShyjH3}sSXx`CqP%?L=)G;QK9 z?70Eqg-O4|4!sKr6+5_D7MtrKx5KKuHPW+C8<>euza>Nc23xNH_}AN+n#+~0+YdbA z=`hg4k4V_!pr7_qR(p7IW;hC(2{LgPfec7wVFx{>yd zUbac$PbL)v)EMMW070Io!;-I$(MzWY;KfaazxSjD2CH6RfMIu5 z)*MAPP!5yYqDbNMp#;boe#g9PePvpkvXps4T4dHT!+-Ws5pP^bpbMb#XH*{Nf-wvT z3X?zuxV%fEGCe2y=qC9Fu-D5aKwTpJ+z4Hhp7S```bt09jaID*bLFw|pR-Y~g~czh ziDQ$`i)wSQiPrLy@lI(bFY0LLJMvAwz0#tyJ9+`R`Gl*?@ysN>m2pyOYI#=E`_#DU zZYRePUqzQ8%~>Xq=MVePP_k}%yVSk)*2QA4<_FW_f97^aB}U}qopNtGRlq>U1!qi} zIDl7=KxxT4uZpT%CC)0c!EH;){dq~baBWx5-$kA`Hc&o13xX-ac3e)uy#dlqrC@QE z!8DVK1p&S~b1S@Bqi?kINU`~o_spK=_fyp7B^UGvPP`0E(~cE2WjYAH4$@hNv=%6> zfXvd9gdCF5THXc%p}hakopZn4Ehdmlh}`F5yzKD`2BO94%A1-Xn5N9aG5p-Z(tV%*WJ;uazCs!|uy$ zzZQ-MSn)*Q9s3T#D%;8{%efShuvkDJ7tX=&(|TKQbHb?(X#FLLjxbqq zY4!MW-j0LT<|vc4jE?)@svaxRDrhhuReU3=TU3=Agm->;{5BE%G0h&ty60VryU=?I zOiuHu^~S0M4;0n*Pv&m>IFbevE6tW%FXFBT3~+wx+QXp~?#uG_DvQjk%teT+a-#hR zmYFx>@pv+ADv>>PzPi6WBBnC?Y6$5Mq*)l5E~Fr!(Gi)K_=wWKc35Sdm$G2A-DP%h z3AWW>{-Y*Io$yfvJl~alJ`&8F$Fx(9GsU%>Om&&uySUb$9#~wnZrC{xDBG)*_af0? z>Kn6Gp2V&z>5J{|Qz=oka~%OnF4SmTaYsydyrEwT(ZDxAFf>Sv>(e3Pb;*&v@|MhW zT(Nhm?V_M;G1tHs7tNeqifHW%YJ@)80?Wn7V=yu!HC^%bP8s=wvb@Fs-U#u@NuOq` z5VL|0jXINj?W6vY?h9Vb^eOe083$1$=2OGePHF!X~!FZ1|hA7dRm(3fLO=KybDXIVsmkgDqRpm?9j zcQi`Rv`r+>T}2eCBr5RvjW#RG{LidEI^7GMfpQ##5tFUcvi$O!MBP}mIWPy^oDC*E zHGLPCiF6J-uQ(rD#)&6Jr zmy|7!*8SUW5^P-hpP?7cNn02*5JjT247!w{g3N`DZ_5(6aVdIAyqZpoaxtWC`_FGDhAXS-3C1^$6EdhjE`0 z)y-Gs=6uC2{v$PKvDhGc-+H=UB>ICU?Ho$*hAj_f62y2JX#2j0-2Gb80RvJztI^7i z3+8BciZ6`TuOwjd(I%G*<2@p=_;a;|ePqJJiN!nQMiQpLlf79eUW>g<<#>bvykU3ptDV@!!?PtgFqj(Ti-xUeROiJX!~ zSbr0(`K(|yEtAG~zq*FRuLKm`#%cX(a?w;Aarp*8M2tr}-cZFu@6D38vvzfJ)u{cM z&7}4!yk4x!C@k>&B{d;dkateITgtUU`Q`LDK6yZ1@|20!jh8po^6cyYj}DL0N@NIY z?*b>hSw;9?siV?Q9uH2*jNn~G;(1|Qh4C1kfZ26(+u)l&xQuY)%lP)6Mbd|)y-N1^dWYsId2#iKRGEo?wG zF64SU=Mdi1OXq~R8%Q$0pN}U6P3qR)H%=2=&gkZ)R-Ps~yW)O_$0K~S*AIZlKOD8o z&2!RUieCglpZnunQ-+m4`ad^@+Im;4h_;&pg0pZoZUv@Q-5Qr3*Z68AlGLbxJ}61+ z5Q5kj>^AjOi`#ulWnQ^B1;{ZmqK9_hFfj?*h1|aw;l-Es-9T0rq$_I@cl*WoPUPg5 z!Ls_xgNp6VH}1^TS1!rCzoR4Eh+q>lytyAB(JsZ|`#Le_%I#^kdZ%|3>m8Ka=@aKk8xfJ=_B&17 zOfh}qYZ3Jk0nBCRdp0S#3w!cqEYn5bcK9gu)9-!J=Bl2vhnnWgr4wy!nU|@C{g7K< zR}QUyqTiZkn#wT*xc>kdR$9!=Eaxu#|F)9nfaiG>nZ+e|Z)jVv@JT8;`QK62dGP-^ zOrARjXG;CMIK;vu@lk`3SIP_-vi&sj{b7wZ3y=1B-m2|?-rv(&I=H#9_SNjcB#RBh zw40|(YoxyNYo6(2dFu80zqOyaD0M7Q4o@6uTRPKN#bv5Y;8#}%iU`uF(zPE8!Z}(C zcA>VOQp~BYJlZUe<<3`$Njqe+d6?`4!3}kzxeG0|QVoAa558|qUM%w%`t{aZAAng5JwCsCjKjOps=u*E;a#;Tu#Z@#>!t@@BrGtR;zt)FKU ze42(+|73EYE^$SN-)xlOKU-;3)wPoPJpB1$$Qz!|smZFDp*>UetvZ-TO7GIIk3t#E zvEkUQUy|pW{QS9Z6lUW;iKqh_-0iXqb_;nR_ursK_2@5B7UJsgigkia*k*%0+eokV zPoI)93$ta9cF@G@;jzDKffv;8Ej|24zcM1&`LF&3mus63Eo+^ByV}JXC*B401DnX{>@?3}q!yXZAzJjnul zzFXC5a4F$>afwZVIp~pf{mk)GJs#ALH(DWxn?H9Hy)yOP!!}hkw-s>cu5$8s( z_jIQAWE6z%APs67sF&Z}?|tL(4)wX!GTSW~K~zKlB{$8+3T9*6`yPdbJ_<3~4+(2% ztLy)=I}uj7NvlasRRd!_GD`P2U32)<8P;N%5|zeRhK)5oN2q64TJFdV)|JFR8~Od; zt5GT29kHbhm1`k7cGtB=MeVyrQgleDxle(L z%+>gt@8hN4zm)1p)wL)sz0@y29XSDkrX4{4-n4uWwWW3I+36dItV-yP6k`SoOy|+% zu|@ZJp$FGtNlT+U&!fs@5I2!)M&-DT+eYisnQ!ZTM&V7^zlVmslu);fi$E6sug-O- z+XuH%>WI07_$i|6@7^bO`yu_vyME1i4317L>4$|w!tuR{0x=n-p`8YsG=ikxKX{Q! zkiy*DTrcxn?zLOr{wac%y@|W~`mL|hgVPCQUU2jI5P2b!H{)KJ#>>LKTMq3@g&dwS zld(Cj`swkcGI`@CM!LN>p4URSGjR`2kC30?uTH)H&;@a8PNa;AdRHUbe(?S5!ewWo zzVR%>V?=+gQN@_k&ypGIg8OE<^iB9~@Pm^i9f$t9<~Y8~L!a=|Qa=Q71;r!(J>s;# z8+>hnz`XQyZOM+^P4(VK8|h7hQlTdXgNYw*HQsG-@%$!3xK&+;F9Mn7$v{8r5T!yw z@($}{ojY^ljY03y^>S|U6|;q3erQ3tX7)Vb!JBI`E#LpW`-X7+@LKfM(z;^8M3x)> zKaYB4ao<^_KHHQLhdWJ`gt&dBl@wZfO?vjPJ-OSYC(mu6~_v)L?MPhfovGhlatLsk&>Y}g@upXm^qu@fZei1L4T8u45e!=vmmitE z(H&<@uFWpKLEF*DEV* z(^#6w*Rq-OYllILx>A4K86}NanA28-EY;a^*@CudN+My+msMwB9yR zx+yWi=;PFdhK?_@J$!&F8e$*%_afHtKu)^8(u_EfudG^dH%#9o{By;#`?{(@3oR^BN0%e*Ne$6zN z#cx(jP^uM){FYFZ?X_y%fkSCijIqkQW(L&DGoYSfo5*^OVVKCMw)o$2?mUo(S)7Ge z;)bHO1>caHbgoLpsXWM9@vr=YIsWwlOMvy?oVZO`rey znxRFoLFwxsOQ$Nt5%1L0eLf(MBM<1%l&Zh5lk>A2rsO(b_VCU}5iD;Zn7{h+K zt5ZVbXD8_ZdMlZspG4T`fG0XdABF`#ne4>axU4HGK`Wd)cB(e5cZ|6?xuD-9HT_=3 z*W3A0ZM{J@eKrWhgAa;|P9W==Y8QPsy^@bZo=eql2L}+{;cv)~Y#A}!Fe*>@T-}_V z-7rmNh~uY6=?-fUTk{>bMbS=l@x2I$(3eK#{R+DI7;;@p?Nn;fjfPIfw!#1XswMj{ z(yVMLy4Y&RV{9~xuV^QeQKQ9X)p6fqYUJ1nmuk}D{7wDMBMK|2Ld;q2l<6J97Q=ieA4Cr z_|Kt0Y+|F*0m4>hE={HywWF6ggSN$Aj6SF&K3zZ*JY3|Rde@*@R|D6OHsmf(Y-H~u z@!Hi9KuN|j-f!tvhJu{9t$JX@)sL^H4vef_h#Oa4B$7wt=49kEhMV%EY(&+_(yrp}tciLS6u6>cfXJ=k?q9I;TQBS*E{EbMGdE zUw7H9O}?C%^ifY;a|-q_OK(q$_3dt>_fxWL6qgK=A64wZHSyx*TX3&TgoxbY*BR@i zEQw{GdDxGe=699$%nEd>i!qw-v@1ve&i`ggag|{8JukI;kXIC8dXS+Eq==C7Iry%sit3$f6(Wz0F zP6!pYx5B&h)|BJPJ7_jUN#nHng*^c~rCwgiI?~+iF7(zb8Igx{;a^L66P2&7zWrKd z^{M&DFXgGJ^@pvnu$>BDut7qCgM$d({LrO3|M7ak=>7Ec3OIo8QdW>(lYv%jSbBvu z=E;3>d2f)g0QLKnhUX(W;Apdk@NHb4$#W2NMZT*}c;dQ+#pfb9wPJf##fzz_Geox) zjJnf;bxQcmc#-UourRE-?ulQXPH0lEzDmE1T5p4ej;*IsHO9(m^JDq$7$=tozg@!> z-2|W7cRcY<^K2#ZahZ3CWhE(7@I5XF^F3~m4Z?KAP)SToWl!D5!gmCevOEu* zcQ|rIMBQ2+uhFwMgfL^70D97>zd6N5mj+bIQk8*Kba*Q` zI`@{a@VX%<_Xh!@8^83=t6wCBI1M3^nbFD}p3?bN%{7@k719cll!+gKtzj;IUeI2y zaX)#2E{10nm`|!L@8~}9_bk)hccta_2K`l;#nvEfo&jf8FE3|~nY8SG8h8ee%2U|< zb?`tK1c^!d^)5T{W+VE^)q;k+wYhtQUj3+LerXF^;eH!?gT${N|Ax2!_HCJr?nb?~ j(C4>s@YNwq96Z09K3(6|-X@UB*vRSp>gxB|GvfaP^yyQz literal 0 HcmV?d00001 diff --git a/test_resources/blackbox/cpp/microqrcode-1/11.txt b/test_resources/blackbox/cpp/microqrcode-1/11.txt new file mode 100644 index 0000000..64209c1 --- /dev/null +++ b/test_resources/blackbox/cpp/microqrcode-1/11.txt @@ -0,0 +1 @@ +Loser \ No newline at end of file diff --git a/test_resources/blackbox/cpp/microqrcode-1/12.jpg b/test_resources/blackbox/cpp/microqrcode-1/12.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4724b6c948f5887d7262cfad1e7191360ab318a7 GIT binary patch literal 19328 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 J#7O@D|JjxW2Jip? literal 0 HcmV?d00001 diff --git a/test_resources/blackbox/cpp/microqrcode-1/12.txt b/test_resources/blackbox/cpp/microqrcode-1/12.txt new file mode 100644 index 0000000..7d3298d --- /dev/null +++ b/test_resources/blackbox/cpp/microqrcode-1/12.txt @@ -0,0 +1 @@ +SN888623311 \ No newline at end of file diff --git a/test_resources/blackbox/cpp/microqrcode-1/3.jpg b/test_resources/blackbox/cpp/microqrcode-1/3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a984ddc0947b00355acb80a17e393f81a60d5f4 GIT binary patch literal 27818 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 literal 0 HcmV?d00001 diff --git a/test_resources/blackbox/cpp/microqrcode-1/7.txt b/test_resources/blackbox/cpp/microqrcode-1/7.txt new file mode 100644 index 0000000..ae75576 --- /dev/null +++ b/test_resources/blackbox/cpp/microqrcode-1/7.txt @@ -0,0 +1 @@ +Victus \ No newline at end of file diff --git a/test_resources/blackbox/cpp/microqrcode-1/9.jpg b/test_resources/blackbox/cpp/microqrcode-1/9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..682f4d11f3a182f5336dc7b412cf67404d58e3f9 GIT binary patch literal 9245 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 literal 0 HcmV?d00001 diff --git a/test_resources/blackbox/cpp/microqrcode-1/M2-Alpha.txt b/test_resources/blackbox/cpp/microqrcode-1/M2-Alpha.txt new file mode 100644 index 0000000..48b83b8 --- /dev/null +++ b/test_resources/blackbox/cpp/microqrcode-1/M2-Alpha.txt @@ -0,0 +1 @@ +ABC \ No newline at end of file diff --git a/test_resources/blackbox/cpp/microqrcode-1/M2-Numeric.png b/test_resources/blackbox/cpp/microqrcode-1/M2-Numeric.png new file mode 100644 index 0000000000000000000000000000000000000000..ea9810f9fb070c7caa238776a53f4653a33f65d6 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^av;pe3?v!<**pYNYymzYuK)l42QotsU9JOCuAVNA zAsp9jk8I>*Fyvu&XuQq7Ua3I(!>cx(ln)F)om9eQK0M*niki};n7=wdtZQ0GcXEFyLWvXpCwM6RgSpag|w4VYS2^wkNMT*IR7nmcHcXEFyLWvXpC+Ss|?Woag{lfH_r6|KthUPb(y!<)X#?eof= y#Tmwayt<74`hZOx?BgOVm)0P zLpZMc9@)rwz<`J40Q+{P^`91mJ-Eu6$s6Z7!R&#_`w7~7KO}F@oyB|oW`0ENclP^h zjAyHUyt<74`hZOx?BgOVmw_O zLpZMc9x>!$rKl{(#``$VCopfG{`@ zl=pBBKzzM;(DXP@I>^)UPiehZ@2f&9nfQlB6E4BW{Gna-2a1MCrzjnkl&NS>363gd zfuFCkA^>mjHx(feAr! zPi%fb!5QVONUMZ<{m*RZm+TR9AZPrXIjrJ4Zn82GfItBd3In0=uP<;z2%HB%i6TYB zFbd)tM!aYoLF?P6fP%p4l>jv&!|=0S$I$)Ysvf8I`;au@hGYY~;C6`)&|aBs);=K^Bu?RfvJW6gy+kE@Jx zQu-nJM&({bQ~dhd{b^1n**6mrA00z#%YyqdNEZKPqx}1Fu(Zz_%7geOoG*BX7B~o-Cv@h*jo` z&daRo(y%kO5wWgB_K2_N=g~`qVP%SFI~ArIvP)j0*Ll9Bu0Bv&e19n~=QJ~PB*`aM zoRT++#MJNelbjia36h_M5M4QNhFo$nx06iw>9QKh zg0ondP2FfYG4>Pp?NhK%?IUaLlLEGs8U^v+E;!v%?NS`m>FEtPPKhx+`-y#EGg&y( zEi>^t-+x0<*43$nUo>uJ&+nk*Z>!yEEj#p@ZrwE%dOt~0Tkc}I5y3VX81b`@uK{6A z`8m^tMVoPIa?95{%i$!Rl;Sm3Q=LhZyhP5db@`W><1T z2)@I(EP)QGKDjn)yhq}q`W?<}|QDm$geig1O{@6>ch zxW@{F1|D^k9;toB3l#q(K$N;!^L^f>bk#9|+J!@4i7>VQ)ZqwoI?wsIM=qH#;N7mi5vMDs>A>+N>3vY<%0@nO6@4HPn%;^bt0znk?rn(qgt5 zhdUT#lW$*Jghti5W%-9@YnA&=35oYmQUf{#J3bc8**HfN1qv=>+PtO_0X!$?`rBP3 z!icsJ&R4Fz;b8dLN{g;#Uh5nBvZ1!QA<=i%IcuT~H1&?z+LN%x)$O3dIG6n}=wo$# zx9K6yK+@XB;)1Z+VRlkj(thKoc)Q*W-Cutzp~sZjtZ&UX{iBVN^LAL+mpJwS%a-6{ujoN&tzL{j}x z)xq()ou%xVx7-OAGIla~n3_H>OW~)%C<)C{cLhG{ZIk?uD^~|Xg8)7Lh-hmDAnJhyko{CP2(h_0G$z(l-E^e9IkUO~h zVay@`wqgT-F5|o%0D9C>6H<}nvFslj9U;$WTrp4MX%xeagsf;D8LV&X^U!Yipm&5_ z9l{@F0D{R||`og7I(U-sBoh{3y-%J|B%A-#MZuE!e3_3 zbQYLmnkLWSb7A$@oBJF6A!fzP zyKzljQyGCjbM>4U35VSh-R&zcaAcFC3vMb_J8^o`vohb@;7*?`3V=YM|0Mz_5Q9`8 zXxM$#0T46*SVQC!CVIoN@`%-isciYsPSm$buF(CbdVnY-MnS{K=SwBGsR7_$q=jQL Us3OC$@&&u8O5s|128Xl%1V(o;jQ{`u literal 0 HcmV?d00001 diff --git a/test_resources/blackbox/cpp/microqrcode-1/MQR-needs-br-update.txt b/test_resources/blackbox/cpp/microqrcode-1/MQR-needs-br-update.txt new file mode 100644 index 0000000..222d0af --- /dev/null +++ b/test_resources/blackbox/cpp/microqrcode-1/MQR-needs-br-update.txt @@ -0,0 +1 @@ +Micro QR Code \ No newline at end of file diff --git a/test_resources/blackbox/cpp/microqrcode-1/NoQuietZone.png b/test_resources/blackbox/cpp/microqrcode-1/NoQuietZone.png new file mode 100644 index 0000000000000000000000000000000000000000..f7a917df87544f92e8df693a4db88545e5f77839 GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LI=AJH&Asp9zPwnJA;2^+s;NY!) z(Z>$XlV@rjb`qD^7Qx*?d@M!N-Yae-&>9U-synmwuM_UzjN0{L9zXKt_4G L`njxgN@xNAb>Ap^ literal 0 HcmV?d00001 diff --git a/test_resources/blackbox/cpp/microqrcode-1/NoQuietZone.txt b/test_resources/blackbox/cpp/microqrcode-1/NoQuietZone.txt new file mode 100644 index 0000000..bd41cba --- /dev/null +++ b/test_resources/blackbox/cpp/microqrcode-1/NoQuietZone.txt @@ -0,0 +1 @@ +12345 \ No newline at end of file diff --git a/test_resources/blackbox/cpp/qrcode-1/1.png b/test_resources/blackbox/cpp/qrcode-1/1.png new file mode 100644 index 0000000000000000000000000000000000000000..0e75f9b61ca15e767a47cb4c03f542c583984bf1 GIT binary patch literal 233653 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≫cpp2V_(F%SJa*q6XW-Xo<-0zPnNm~VK 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 literal 0 HcmV?d00001 diff --git a/test_resources/blackbox/cpp/qrcode-1/1.txt b/test_resources/blackbox/cpp/qrcode-1/1.txt new file mode 100644 index 0000000..cb6b805 --- /dev/null +++ b/test_resources/blackbox/cpp/qrcode-1/1.txt @@ -0,0 +1 @@ +MEBKM:URL:http\://en.wikipedia.org/wiki/Main_Page;; \ No newline at end of file diff --git a/test_resources/blackbox/cpp/qrcode-1/11.png b/test_resources/blackbox/cpp/qrcode-1/11.png new file mode 100644 index 0000000000000000000000000000000000000000..ccec513c91eb7fa60b65646fc9e374331253f32d GIT binary patch literal 218288 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%0DtX8fFuJQTQ(?LOPULgWw8 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

`+33sa}#YXcz}rQ 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