From e49f873bc91daafe2c7a08215c8beceb49a24442 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 13 Mar 2023 12:35:58 -0500 Subject: [PATCH] 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;