From bd2189cf08d94244c989c10cb3675c6b92ae510e Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 10 Mar 2023 13:36:05 -0600 Subject: [PATCH] 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;