diff --git a/Cargo.toml b/Cargo.toml index 57c6df5..001df47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rxing" -version = "0.2.18" +version = "0.2.19" description="A rust port of the zxing barcode library." license="Apache-2.0" repository="https://github.com/hschimke/rxing" @@ -42,4 +42,4 @@ image = ["dep:image", "dep:imageproc"] allow_forced_iso_ied_18004_compliance = [] svg_write = ["dep:svg"] svg_read = ["dep:resvg", "image"] -wasm_support = ["chrono/wasmbind"] +wasm_support = ["chrono/wasmbind"] \ No newline at end of file diff --git a/README.md b/README.md index 8509181..64cf48f 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ fn main() { ``` ## Latest Release Notes +* *v0.2.19* -> The datamatrix detector for the c++ version of zxing [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp) has been ported. This features a dramatically different method of detecting datamatrix symbols. If you want to fallback to the original version, include the decode hint TRY_HARDER. * *v0.2.15* -> Support for reading and writing svg files through the feature flags `svg_read` and `svg_write`. These flags are off by default. @@ -73,3 +74,6 @@ fn main() { ## ZXing Track Currently tracking zxing 3.5.1 + +## Copyright notes +The original license remains with the zxing developers. The license for the ported components of the c++ port remain with the developers of that port, where applicable. \ No newline at end of file diff --git a/src/common/bit_matrix.rs b/src/common/bit_matrix.rs index 397dac0..568ad9b 100644 --- a/src/common/bit_matrix.rs +++ b/src/common/bit_matrix.rs @@ -20,7 +20,7 @@ use std::fmt; -use crate::Exceptions; +use crate::{Exceptions, RXingResultPoint}; use super::BitArray; @@ -207,6 +207,12 @@ impl BitMatrix { ((self.bits[offset] >> (x & 0x1f)) & 1) != 0 } + pub fn get_point(&self, point: &RXingResultPoint) -> bool { + self.get(point.x as u32, point.y as u32) + // let offset = self.get_offset(point.y as u32, point.x as u32); + // ((self.bits[offset] >> (x & 0x1f)) & 1) != 0 + } + #[inline(always)] fn get_offset(&self, y: u32, x: u32) -> usize { y as usize * self.row_size + (x as usize / 32) @@ -659,6 +665,13 @@ impl BitMatrix { } new_bm } + + pub fn isIn(&self, p: &RXingResultPoint, b: i32) -> bool { + b as f32 <= p.x + && p.x < self.getWidth() as f32 - b as f32 + && b as f32 <= p.y + && p.y < self.getHeight() as f32 - b as f32 + } } impl fmt::Display for BitMatrix { diff --git a/src/datamatrix/data_matrix_reader.rs b/src/datamatrix/data_matrix_reader.rs index 3d66eb6..48a66d0 100644 --- a/src/datamatrix/data_matrix_reader.rs +++ b/src/datamatrix/data_matrix_reader.rs @@ -22,7 +22,10 @@ use crate::{ RXingResultMetadataType, RXingResultMetadataValue, Reader, }; -use super::{decoder::Decoder, detector::Detector}; +use super::{ + decoder::Decoder, + detector::{zxing_cpp_detector, Detector}, +}; use once_cell::sync::Lazy; @@ -69,13 +72,10 @@ impl Reader for DataMatrixReader { image: &mut crate::BinaryBitmap, hints: &crate::DecodingHintDictionary, ) -> Result { - let try_harder = if let Some(DecodeHintValue::TryHarder(true)) = - hints.get(&DecodeHintType::TRY_HARDER) - { - true - } else { - false - }; + let try_harder = matches!( + hints.get(&DecodeHintType::TRY_HARDER), + Some(DecodeHintValue::TryHarder(true)) + ); let decoderRXingResult; let mut points = Vec::new(); if hints.contains_key(&DecodeHintType::PURE_BARCODE) { @@ -85,15 +85,25 @@ impl Reader for DataMatrixReader { } else { //Result decoderRXingResult = if let Ok(fnd) = || -> Result { - let detectorRXingResult = Detector::new(image.getBlackMatrix())?.detect()?; + let detectorRXingResult = + zxing_cpp_detector::detect(image.getBlackMatrix(), try_harder, true)?; let decoded = DECODER.decode(detectorRXingResult.getBits())?; points = detectorRXingResult.getPoints().to_vec(); Ok(decoded) }() { fnd } else if try_harder { - let bits = self.extractPureBits(image.getBlackMatrix())?; - DECODER.decode(&bits)? + if let Ok(fnd) = || -> Result { + let detectorRXingResult = Detector::new(image.getBlackMatrix())?.detect()?; + let decoded = DECODER.decode(detectorRXingResult.getBits())?; + points = detectorRXingResult.getPoints().to_vec(); + Ok(decoded) + }() { + fnd + } else { + let bits = self.extractPureBits(image.getBlackMatrix())?; + DECODER.decode(&bits)? + } } else { return Err(Exceptions::NotFoundException(None)); }; diff --git a/src/datamatrix/detector/mod.rs b/src/datamatrix/detector/mod.rs index 0c11812..0de94ac 100644 --- a/src/datamatrix/detector/mod.rs +++ b/src/datamatrix/detector/mod.rs @@ -2,3 +2,4 @@ mod datamatrix_detector; mod datamatrix_result; pub use datamatrix_detector::*; pub use datamatrix_result::*; +pub mod zxing_cpp_detector; diff --git a/src/datamatrix/detector/zxing_cpp_detector/bitmatrix_cursor.rs b/src/datamatrix/detector/zxing_cpp_detector/bitmatrix_cursor.rs new file mode 100644 index 0000000..e4d94a3 --- /dev/null +++ b/src/datamatrix/detector/zxing_cpp_detector/bitmatrix_cursor.rs @@ -0,0 +1,173 @@ +use crate::RXingResultPoint; + +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: &RXingResultPoint) -> Value; //const + // { + // return img->isIn(p) ? Value{img->get(p)} : Value{}; + // } + + fn blackAt(&self, pos: &RXingResultPoint) -> bool { + self.testAt(pos).isBlack() + } + fn whiteAt(&self, pos: &RXingResultPoint) -> bool { + self.testAt(pos).isWhite() + } + + fn isIn(&self, p: &RXingResultPoint) -> 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) -> &RXingResultPoint; //{ return d; } + fn back(&self) -> RXingResultPoint; // { return {-d.x, -d.y}; } + fn left(&self) -> RXingResultPoint; //{ return {d.y, -d.x}; } + fn right(&self) -> RXingResultPoint; //{ return {-d.y, d.x}; } + fn direction(&self, dir: Direction) -> RXingResultPoint { + 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: &RXingResultPoint) -> 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: &RXingResultPoint); // { d = bresenhamDirection(dir); } + // fn setDirection(&self, dir:&RXingResultPoint);// { d = dir; } + + fn step(&mut self, s: Option) -> bool; // DEF to 1 + // { + // p += s * d; + // return isIn(p); + // } + + fn movedBy(self, d: &RXingResultPoint) -> Self; + // { + // 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: Option) -> i32 { + let mut range = if let Some(r) = range { r } else { 0 }; + let mut res = 0; + + let mut steps = self.stepToEdge(Some(1), Some(range), None); + + while steps > 0 { + range -= steps; + res += 1; + steps = self.stepToEdge(Some(1), Some(range), None); + } + + res + } + + // 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/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs b/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs new file mode 100644 index 0000000..51ce517 --- /dev/null +++ b/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs @@ -0,0 +1,364 @@ +macro_rules! CHECK { + ($A:expr) => { + if (!($A)) { + continue; + } + }; +} + +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +use std::{cell::RefCell, rc::Rc}; + +use crate::{ + common::{BitMatrix, DefaultGridSampler, GridSampler}, + datamatrix::detector::{ + zxing_cpp_detector::{util::intersect, BitMatrixCursor, Quadrilateral, RegressionLine}, + DatamatrixDetectorResult, + }, + qrcode::encoder::ByteMatrix, + result_point_utils::distance, + Exceptions, RXingResultPoint, ResultPoint, +}; + +use super::{DMRegressionLine, EdgeTracer}; + +/** +* The following code is the 'new' one implemented by Axel Waggershauser and is working completely different. +* It is performing something like a (back) trace search along edges through the bit matrix, first looking for +* the 'L'-pattern, then tracing the black/white borders at the top/right. Advantages over the old code are: +* * works with lower resolution scans (around 2 pixel per module), due to sub-pixel precision grid placement +* * works with real-world codes that have just one module wide quiet-zone (which is perfectly in spec) +*/ + +fn Scan( + startTracer: &mut EdgeTracer, + lines: &mut [DMRegressionLine; 4], +) -> Result { + while startTracer.step(None) { + //log(startTracer.p); + + // continue until we cross from black into white + if !startTracer.edgeAtBack().isWhite() { + continue; + } + + let mut tl = RXingResultPoint::default(); + let mut bl = RXingResultPoint::default(); + let mut br = RXingResultPoint::default(); + let mut tr = RXingResultPoint::default(); + + for l in lines.iter_mut() { + l.reset(); + } + + let [lineL, lineB, lineR, lineT] = lines; + + // for l in lines { + // l.reset(); + // } + + // #ifdef PRINT_DEBUG + // SCOPE_EXIT([&] { + // for (auto& l : lines) + // log(l.points()); + // }); + // # define CHECK(A) if (!(A)) { printf("broke at %d\n", __LINE__); continue; } + // #else + // # define CHECK(A) if(!(A)) continue + // #endif + + let mut t = startTracer.clone(); + + // follow left leg upwards + t.turnRight(); + t.state = 1; + CHECK!(t.traceLine(&t.right(), lineL)?); + CHECK!(t.traceCorner(&mut t.right(), &mut tl)?); + lineL.reverse(); + let mut tlTracer = t; + + // follow left leg downwards + t = startTracer.clone(); + t.state = 1; + t.setDirection(&tlTracer.right()); + CHECK!(t.traceLine(&t.left(), lineL)?); + if !lineL.isValid() { + t.updateDirectionFromOrigin(&tl); + } + let up = t.back(); + CHECK!(t.traceCorner(&mut t.left(), &mut bl)?); + + // follow bottom leg right + t.state = 2; + CHECK!(t.traceLine(&t.left(), lineB)?); + if !lineB.isValid() { + t.updateDirectionFromOrigin(&bl); + } + let right = *t.front(); + CHECK!(t.traceCorner(&mut t.left(), &mut br)?); + + let lenL = distance(&tl, &bl) - 1.0; + let lenB = distance(&bl, &br) - 1.0; + CHECK!(lenL >= 8.0 && lenB >= 10.0 && lenB >= lenL / 4.0 && lenB <= lenL * 18.0); + + let mut maxStepSize: i32 = (lenB / 5.0 + 1.0) as i32; // datamatrix bottom dim is at least 10 + + // at this point we found a plausible L-shape and are now looking for the b/w pattern at the top and right: + // follow top row right 'half way' (4 gaps), see traceGaps break condition with 'invalid' line + tlTracer.setDirection(&right); + CHECK!(tlTracer.traceGaps( + &tlTracer.right(), + lineT, + maxStepSize, + &mut DMRegressionLine::default() + )?); + + // let a = lineT.length() as i32 / 3; + // let b = (lenL / 5.0) as i32; + + // maxStepSize = std::cmp::min(a, b) * 2; + maxStepSize = std::cmp::min(lineT.length() as i32 / 3, (lenL / 5.0) as i32) * 2; + + // follow up until we reach the top line + t.setDirection(&up); + t.state = 3; + CHECK!(t.traceGaps(&t.left(), lineR, maxStepSize, lineT)?); + CHECK!(t.traceCorner(&mut t.left(), &mut tr)?); + + let lenT = distance(&tl, &tr) - 1.0; + let lenR = distance(&tr, &br) - 1.0; + + CHECK!( + (lenT - lenB).abs() / lenB < 0.5 + && (lenR - lenL).abs() / lenL < 0.5 + && lineT.points().len() >= 5 + && lineR.points().len() >= 5 + ); + + // continue top row right until we cross the right line + CHECK!(tlTracer.traceGaps(&tlTracer.right(), lineT, maxStepSize, lineR)?); + + // #ifdef PRINT_DEBUG + // printf("L: %.1f, %.1f ^ %.1f, %.1f > %.1f, %.1f (%d : %d : %d : %d)\n", bl.x, bl.y, + // tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, (int)lenL, (int)lenB, (int)lenT, (int)lenR); + // #endif + + // for l in [lineL, lineB, lineT, lineR] { + // l.evaluate_max_distance(Some(1.0), None); + // } + lineL.evaluate_max_distance(Some(1.0), None); + lineB.evaluate_max_distance(Some(1.0), None); + lineT.evaluate_max_distance(Some(1.0), None); + lineR.evaluate_max_distance(Some(1.0), None); + + // find the bounding box corners of the code with sub-pixel precision by intersecting the 4 border lines + bl = intersect(lineB, lineL)?; + tl = intersect(lineT, lineL)?; + tr = intersect(lineT, lineR)?; + br = intersect(lineB, lineR)?; + + let mut dimT: i32 = 0; + let mut dimR: i32 = 0; + let mut fracT: f64 = 0.0; + let mut fracR: f64 = 0.0; + let splitDouble = |d: f64, i: &mut i32, f: &mut f64| { + *i = if d.is_normal() { (d + 0.5) as i32 } else { 0 }; + *f = if d.is_normal() { + (d - *i as f64).abs() + } else { + f64::INFINITY + }; + }; + splitDouble(lineT.modules(&tl, &tr)?, &mut dimT, &mut fracT); + splitDouble(lineR.modules(&br, &tr)?, &mut dimR, &mut fracR); + + // #ifdef PRINT_DEBUG + // printf("L: %.1f, %.1f ^ %.1f, %.1f > %.1f, %.1f ^> %.1f, %.1f\n", bl.x, bl.y, + // tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, tr.x, tr.y); + // printf("dim: %d x %d\n", dimT, dimR); + // #endif + + // if we have an almost square (invalid rectangular) data matrix dimension, we try to parse it by assuming a + // square. we use the dimension that is closer to an integral value. all valid rectangular symbols differ in + // their dimension by at least 10 (here 5, see doubling below). Note: this is currently not required for the + // black-box tests to complete. + if (dimT - dimR).abs() < 5 { + dimR = if fracR < fracT { dimR } else { dimT }; + dimT = dimR; + } + + // the dimension is 2x the number of black/white transitions + dimT *= 2; + dimR *= 2; + + CHECK!((10..=144).contains(&dimT) && (8..=144).contains(&dimR)); + + let movedTowardsBy = |a: &RXingResultPoint, + b1: &RXingResultPoint, + b2: &RXingResultPoint, + d: f32| + -> RXingResultPoint { + *a + d * RXingResultPoint::normalized( + RXingResultPoint::normalized(*b1 - *a) + RXingResultPoint::normalized(*b2 - *a), + ) + }; + + // shrink shape by half a pixel to go from center of white pixel outside of code to the edge between white and black + let sourcePoints = Quadrilateral::with_points( + movedTowardsBy(&tl, &tr, &bl, 0.5), + // move the tr point a little less because the jagged top and right line tend to be statistically slightly + // inclined toward the center anyway. + movedTowardsBy(&tr, &br, &tl, 0.3), + movedTowardsBy(&br, &bl, &tr, 0.5), + movedTowardsBy(&bl, &tl, &br, 0.5), + ); + + let grid_sampler = DefaultGridSampler::default(); + // let transform = PerspectiveTransform::quadrilateralToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3, x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); + + let res = grid_sampler.sample_grid_detailed( + startTracer.img, + dimT as u32, + dimR as u32, + 0.0, + 0.0, + dimT as f32, + 0.0, + dimT as f32, + dimR as f32, + 0.0, + dimR as f32, + sourcePoints.topLeft().getX(), + sourcePoints.topLeft().getY(), + sourcePoints.topRight().getX(), + sourcePoints.topRight().getY(), + sourcePoints.bottomRight().getX(), + sourcePoints.bottomRight().getY(), + sourcePoints.bottomLeft().getX(), + sourcePoints.bottomLeft().getY(), + ); + + // let res = grid_sampler.sample_grid(startTracer.img, dimT as u32, dimR as u32, &transform); + + // let res = SampleGrid(*startTracer.img, dimT, dimR, PerspectiveTransform(Rectangle(dimT, dimR, 0), sourcePoints)); + + CHECK!(res.is_ok()); + + return Ok(DatamatrixDetectorResult::new( + res.unwrap(), + sourcePoints.points().to_vec(), + )); + } + + Err(Exceptions::NotFoundException(None)) +} + +pub fn detect( + image: &BitMatrix, + tryHarder: bool, + tryRotate: bool, +) -> Result { + // #ifdef PRINT_DEBUG + // LogMatrixWriter lmw(log, image, 1, "dm-log.pnm"); + // // tryRotate = tryHarder = false; + // #endif + + // disable expensive multi-line scan to detect off-center symbols for now + // #ifndef __cpp_impl_coroutine + // tryHarder = false; + // #endif + + // a history log to remember where the tracing already passed by to prevent a later trace from doing the same work twice + let mut history = None; + if tryHarder { + history = Some(Rc::new(RefCell::new(ByteMatrix::new( + image.getWidth(), + image.getHeight(), + )))); + } + + // instantiate RegressionLine objects outside of Scan function to prevent repetitive std::vector allocations + let mut lines = [ + DMRegressionLine::default(), + DMRegressionLine::default(), + DMRegressionLine::default(), + DMRegressionLine::default(), + ]; // [DMRegressionLine::default();4]; + + const MIN_SYMBOL_SIZE: u32 = 8 * 2; // minimum realistic size in pixel: 8 modules x 2 pixels per module + + for dir in [ + RXingResultPoint { x: -1.0, y: 0.0 }, + RXingResultPoint { x: 1.0, y: 0.0 }, + RXingResultPoint { x: 0.0, y: -1.0 }, + RXingResultPoint { x: 0.0, y: 1.0 }, + ] { + // for (auto dir : {PointF(-1, 0), PointF(1, 0), PointF(0, -1), PointF(0, 1)}) { + let center = RXingResultPoint { + x: (image.getWidth() / 2) as f32, + y: (image.getHeight() / 2) as f32, + }; //PointF(image.width() / 2, image.height() / 2); + let startPos = + RXingResultPoint::centered(&(center - center * dir + MIN_SYMBOL_SIZE as i32 / 2 * dir)); + + if let Some(history) = &mut history { + history.borrow_mut().clear(0); + // history.clear(0); + } + + let mut i = 1; + loop { + // for (int i = 1;; ++i) { + // EdgeTracer tracer(image, startPos, dir); + let mut tracer = EdgeTracer::new(image, startPos, dir); + tracer.p += i / 2 + * MIN_SYMBOL_SIZE as i32 + * (if (i & 1) != 0 { -1 } else { 1 }) + * tracer.right(); + if tryHarder { + // tracer.history = history.as_mut(); + tracer.history = history.clone(); + // if let Some(history) = &history { + // tracer.history = history; + // } + // tracer.history = &history; + } + + if !tracer.isInSelf() { + break; + } + + // #ifdef __cpp_impl_coroutine + // DetectorResult res; + // while (res = Scan(tracer, lines), res.isValid()) + // co_yield std::move(res); + // #else + if let Ok(res) = Scan(&mut tracer, &mut lines) { + // if res.isValid(){ + return Ok(res); + // } + } + + // if (auto res = Scan(tracer, lines); res.isValid()) + // {return res;} + // #endif + + if !tryHarder { + break; + } // only test center lines + i += 1; + } + + if !tryRotate { + break; + } // only test left direction + } + + // #ifndef __cpp_impl_coroutine + Err(Exceptions::NotFoundException(None)) + // #endif +} diff --git a/src/datamatrix/detector/zxing_cpp_detector/direction.rs b/src/datamatrix/detector/zxing_cpp_detector/direction.rs new file mode 100644 index 0000000..15fb6ac --- /dev/null +++ b/src/datamatrix/detector/zxing_cpp_detector/direction.rs @@ -0,0 +1,14 @@ +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum Direction { + Left = -1, + Right = 1, +} + +impl From for i32 { + fn from(value: Direction) -> Self { + match value { + Direction::Left => -1, + Direction::Right => 1, + } + } +} diff --git a/src/datamatrix/detector/zxing_cpp_detector/dm_regression_line.rs b/src/datamatrix/detector/zxing_cpp_detector/dm_regression_line.rs new file mode 100644 index 0000000..bd83515 --- /dev/null +++ b/src/datamatrix/detector/zxing_cpp_detector/dm_regression_line.rs @@ -0,0 +1,316 @@ +use crate::{Exceptions, RXingResultPoint}; + +use super::{ + util::{float_max, float_min}, + RegressionLine, +}; + +#[derive(Clone)] +pub struct DMRegressionLine { + points: Vec, + direction_inward: RXingResultPoint, + 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; +} + +impl Default for DMRegressionLine { + fn default() -> Self { + Self { + points: Default::default(), + direction_inward: Default::default(), + a: f32::NAN, + b: f32::NAN, + c: f32::NAN, + } + } +} + +impl RegressionLine for DMRegressionLine { + // fn intersect( + // &self, + // l1: &T, + // l2: &T2, + // ) -> RXingResultPoint { + // int + // } + + fn points(&self) -> &[RXingResultPoint] { + &self.points + } + + fn length(&self) -> u32 { + if self.points.len() >= 2 { + RXingResultPoint::distance(*self.points.first().unwrap(), *self.points.last().unwrap()) + as u32 + } else { + 0 + } + } + + fn isValid(&self) -> bool { + !self.a.is_nan() + } + + fn normal(&self) -> RXingResultPoint { + if self.isValid() { + RXingResultPoint { + x: self.a, + y: self.b, + } + } else { + self.direction_inward + } + } + + fn signedDistance(&self, p: &RXingResultPoint) -> f32 { + RXingResultPoint::dot(self.normal(), *p) - self.c + } + + fn distance_single(&self, p: &RXingResultPoint) -> f32 { + (self.signedDistance(p)).abs() + } + + fn reset(&mut self) { + self.points.clear(); + self.direction_inward = RXingResultPoint { x: 0.0, y: 0.0 }; + self.a = f32::NAN; + self.b = f32::NAN; + self.c = f32::NAN; + } + + fn add(&mut self, p: &RXingResultPoint) -> Result<(), Exceptions> { + if self.direction_inward == RXingResultPoint::default() { + return Err(Exceptions::IllegalStateException(None)); + } + self.points.push(*p); + if self.points.len() == 1 { + self.c = RXingResultPoint::dot(self.normal(), *p); + } + Ok(()) + } + + fn pop_back(&mut self) { + self.points.pop(); + } + + fn setDirectionInward(&mut self, d: &RXingResultPoint) { + self.direction_inward = RXingResultPoint::normalized(*d); + } + + fn evaluate_max_distance( + &mut self, + maxSignedDist: Option, + updatePoints: Option, + ) -> bool { + let maxSignedDist = if let Some(m) = maxSignedDist { m } else { -1.0 }; + let updatePoints = if let Some(u) = updatePoints { u } else { false }; + + 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); + } + + 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 = 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); + } + let diff = max - min; + let len = RXingResultPoint::maxAbsComponent(&diff); + let steps = float_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: &[RXingResultPoint]) -> 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 RXingResultPoint::dot(self.direction_inward, self.normal()) < 0.0 { + // if (dot(_directionInward, normal()) < 0) { + self.a = -self.a; + self.b = -self.b; + } + self.c = RXingResultPoint::dot(self.normal(), mean); // (a*mean.x + b*mean.y); + RXingResultPoint::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 RXingResultPoint::dot(self.direction_inward, self.normal()) < 0.0 { + // if (dot(_directionInward, normal()) < 0) { + self.a = -self.a; + self.b = -self.b; + } + self.c = RXingResultPoint::dot(self.normal(), mean); // (a*mean.x + b*mean.y); + RXingResultPoint::dot(self.direction_inward, self.normal()) > 0.5 + // angle between original and new direction is at most 60 degree + } +} + +impl DMRegressionLine { + // template + fn average(c: &[f64], f: T) -> f64 + where + T: Fn(f64) -> bool, + { + let mut sum: f64 = 0.0; + let mut num = 0; + for v in c { + // for (const auto& v : c) + if f(*v) { + sum += *v; + num += 1; + } + } + sum / num as f64 + } + + pub fn reverse(&mut self) { + self.points.reverse(); + } + + pub fn modules( + &mut self, + beg: &RXingResultPoint, + end: &RXingResultPoint, + ) -> Result { + if self.points.len() <= 3 { + return Err(Exceptions::IllegalStateException(None)); + } + + // re-evaluate and filter out all points too far away. required for the gapSizes calculation. + self.evaluate_max_distance(Some(1.0), Some(true)); + + // std::vector gapSizes, modSizes; + let mut gapSizes: Vec = Vec::new(); + let mut modSizes = Vec::new(); + + gapSizes.reserve(self.points.len()); + + // calculate the distance between the points projected onto the regression line + for i in 1..self.points.len() { + // for (size_t i = 1; i < _points.size(); ++i) + gapSizes.push(self.distance( + &self.project(&self.points[i]), + &self.project(&self.points[i - 1]), + ) as f64); + } + + // calculate the (expected average) distance of two adjacent pixels + let unitPixelDist = RXingResultPoint::length(RXingResultPoint::bresenhamDirection( + &(*self.points.last().unwrap() - *self.points.first().unwrap()), + )) as f64; + + // calculate the width of 2 modules (first black pixel to first black pixel) + let mut sumFront: f64 = + self.distance(beg, &self.project(&self.points[0])) as f64 - unitPixelDist; + let mut sumBack: f64 = 0.0; // (last black pixel to last black pixel) + for dist in gapSizes { + // for (auto dist : gapSizes) { + if dist > 1.9 * unitPixelDist { + modSizes.push(std::mem::take(&mut sumBack)); + } + sumFront += dist; + sumBack += dist; + if dist > 1.9 * unitPixelDist { + modSizes.push(std::mem::take(&mut sumFront)); + } + } + + modSizes + .push(sumFront + self.distance(end, &self.project(self.points.last().unwrap())) as f64); + modSizes[0] = 0.0; // the first element is an invalid sumBack value, would be pop_front() if vector supported this + let lineLength = self.distance(beg, end) as f64 - unitPixelDist; + let mut meanModSize = Self::average(&modSizes, |_: f64| true); + // let meanModSize = average(modSizes, [](double){ return true; }); + // #ifdef PRINT_DEBUG + // printf("unit pixel dist: %.1f\n", unitPixelDist); + // printf("lineLength: %.1f, meanModSize: %.1f, gaps: %lu\n", lineLength, meanModSize, modSizes.size()); + // #endif + for i in 0..2 { + // for (int i = 0; i < 2; ++i) + meanModSize = Self::average(&modSizes, |dist: f64| { + (dist - meanModSize).abs() < meanModSize / (2 + i) as f64 + }); + // meanModSize = average(modSizes, [=](double dist) { return std::abs(dist - meanModSize) < meanModSize / (2 + i); }); + } + // #ifdef PRINT_DEBUG + // printf("post filter meanModSize: %.1f\n", meanModSize); + // #endif + Ok(lineLength / meanModSize) + } +} diff --git a/src/datamatrix/detector/zxing_cpp_detector/edge_tracer.rs b/src/datamatrix/detector/zxing_cpp_detector/edge_tracer.rs new file mode 100644 index 0000000..debe34e --- /dev/null +++ b/src/datamatrix/detector/zxing_cpp_detector/edge_tracer.rs @@ -0,0 +1,414 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::{common::BitMatrix, qrcode::encoder::ByteMatrix, Exceptions, RXingResultPoint}; + +use super::{BitMatrixCursor, Direction, RegressionLine, StepResult, Value}; + +#[derive(Clone)] +pub struct EdgeTracer<'a> { + pub(super) img: &'a BitMatrix, + + pub(super) p: RXingResultPoint, // current position + d: RXingResultPoint, // current direction + + // pub history: Option<&'a mut ByteMatrix>, // = nullptr; + pub history: Option>>, + pub state: i32, + // const BitMatrix* img; + + // POINT p; // current position + // POINT d; // current direction +} + +// impl<'a> Clone for EdgeTracer<'_> { +// fn clone(&self) -> Self { +// if let Some(history) = self.history { +// Self { img: self.img, p: self.p.clone(), d: self.d.clone(), history: Some(history), state: self.state.clone() } +// }else { +// Self { img: self.img, p: self.p.clone(), d: self.d.clone(), history: None, state: self.state.clone() } +// } +// } +// } + +impl BitMatrixCursor for EdgeTracer<'_> { + fn testAt(&self, p: &RXingResultPoint) -> Value { + if self.img.isIn(p, 0) { + Value::from(self.img.get_point(p)) + } else { + Value::Invalid + } + } + + fn isIn(&self, p: &RXingResultPoint) -> bool { + self.img.isIn(p, 0) + } + + fn isInSelf(&self) -> bool { + self.isIn(&self.p) + } + + fn isBlack(&self) -> bool { + self.blackAt(&self.p) + } + + fn isWhite(&self) -> bool { + self.whiteAt(&self.p) + } + + fn front(&self) -> &RXingResultPoint { + &self.d + } + + fn back(&self) -> RXingResultPoint { + RXingResultPoint { + x: -self.d.x, + y: -self.d.y, + } + } + + fn left(&self) -> RXingResultPoint { + RXingResultPoint { + x: self.d.y, + y: -self.d.x, + } + } + + fn right(&self) -> RXingResultPoint { + RXingResultPoint { + x: -self.d.y, + y: self.d.x, + } + } + + fn turnBack(&mut self) { + self.d = self.back() + } + + fn turnLeft(&mut self) { + self.d = self.left() + } + + fn turnRight(&mut self) { + self.d = self.right() + } + + fn turn(&mut self, dir: Direction) { + self.d = self.direction(dir) + } + + fn edgeAt_point(&self, d: &RXingResultPoint) -> Value { + let v = self.testAt(&self.p); + if self.testAt(&(self.p + *d)) != v { + v + } else { + Value::Invalid + } + } + + fn setDirection(&mut self, dir: &RXingResultPoint) { + self.d = RXingResultPoint::bresenhamDirection(dir) + } + + fn step(&mut self, s: Option) -> bool { + let s = if let Some(s) = s { s } else { 1.0 }; + self.p += self.d * s; + self.isIn(&self.p) + } + + fn movedBy(self, d: &RXingResultPoint) -> Self { + let mut res = self; + res.p += *d; + + 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 { + 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 }; + // 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); + + while nth > 0 && (range <= 0 || steps < range) && lv.isValid() { + steps += 1; + let v = self.testAt(&(self.p + steps * self.d)); + if lv != v { + lv = v; + nth -= 1; + } + } + if backup { + steps -= 1; + } + self.p += self.d * steps; + steps * i32::from(nth == 0) + } +} + +impl<'a> EdgeTracer<'_> { + pub fn new(image: &'a BitMatrix, p: RXingResultPoint, d: RXingResultPoint) -> EdgeTracer<'a> { + // : img(&image), p(p) { setDirection(d); } + EdgeTracer { + img: image, + p, + d, + history: None, + state: 0, + } + } + + fn traceStep( + &mut self, + dEdge: &RXingResultPoint, + maxStepSize: i32, + goodDirection: bool, + ) -> Result { + let dEdge = RXingResultPoint::mainDirection(*dEdge); + for breadth in 1..=(if maxStepSize == 1 { + 2 + } else if goodDirection { + 1 + } else { + 3 + }) { + // for (int breadth = 1; breadth <= (maxStepSize == 1 ? 2 : (goodDirection ? 1 : 3)); ++breadth) + for step in 1..=maxStepSize { + // for (int step = 1; step <= maxStepSize; ++step) + for i in 0..=(2 * (step / 4 + 1) * breadth) { + // for (int i = 0; i <= 2*(step/4+1) * breadth; ++i) { + let mut pEdge = self.p + + step * self.d + + (if i & 1 > 0 { (i + 1) / 2 } else { -i / 2 }) * dEdge; + // dbg!(pEdge); + + if !self.blackAt(&(pEdge + dEdge)) { + continue; + } + + // found black pixel -> go 'outward' until we hit the b/w border + for _j in 0..(std::cmp::max(maxStepSize, 3)) { + // for (int j = 0; j < std::max(maxStepSize, 3) && isIn(pEdge); ++j) { + if self.whiteAt(&pEdge) { + // if we are not making any progress, we still have another endless loop bug + if self.p == RXingResultPoint::centered(&pEdge) { + return Err(Exceptions::IllegalStateException(None)); + } + self.p = RXingResultPoint::centered(&pEdge); + + // if (self.history && maxStepSize == 1) { + if let Some(history) = &self.history { + if maxStepSize == 1 { + if history.borrow().get(self.p.x as u32, self.p.y as u32) + == self.state as u8 + { + return Ok(StepResult::ClosedEnd); + } + history.borrow_mut().set( + self.p.x as u32, + self.p.y as u32, + self.state as u8, + ); + } + } + + return Ok(StepResult::Found); + } + pEdge = pEdge - dEdge; + if self.blackAt(&(pEdge - self.d)) { + pEdge = pEdge - self.d; + } + // dbg!(pEdge); + + if !self.isIn(&pEdge) { + break; + } + } + // no valid b/w border found within reasonable range + return Ok(StepResult::ClosedEnd); + } + } + } + Ok(StepResult::OpenEnd) + } + + pub fn updateDirectionFromOrigin(&mut self, origin: &RXingResultPoint) -> bool { + let old_d = self.d; + self.setDirection(&(self.p - origin)); + // if the new direction is pointing "backward", i.e. angle(new, old) > 90 deg -> break + if RXingResultPoint::dot(self.d, old_d) < 0.0 { + return false; + } + // make sure d stays in the same quadrant to prevent an infinite loop + if (self.d.x).abs() == (self.d.y).abs() { + self.d = RXingResultPoint::mainDirection(old_d) + + 0.99 * (self.d - RXingResultPoint::mainDirection(old_d)); + } else if RXingResultPoint::mainDirection(self.d) != RXingResultPoint::mainDirection(old_d) + { + self.d = RXingResultPoint::mainDirection(old_d) + + 0.99 * RXingResultPoint::mainDirection(self.d); + } + true + } + + pub fn traceLine( + &mut self, + dEdge: &RXingResultPoint, + line: &mut T, + ) -> Result { + line.setDirectionInward(dEdge); + loop { + // log(self.p); + line.add(&self.p)?; + if line.points().len() % 50 == 10 { + if !line.evaluate_max_distance(None, None) { + return Ok(false); + } + if !self.updateDirectionFromOrigin( + &(self.p - line.project(&self.p) + **line.points().first().as_ref().unwrap()), + ) { + return Ok(false); + } + } + let stepResult = self.traceStep(dEdge, 1, line.isValid())?; + if stepResult != StepResult::Found { + return Ok(stepResult == StepResult::OpenEnd && line.points().len() > 1); + } + } // while (true); + } + + pub fn traceGaps( + &mut self, + dEdge: &RXingResultPoint, + line: &mut T, + maxStepSize: i32, + finishLine: &mut T, + ) -> Result { + let mut maxStepSize = maxStepSize; + line.setDirectionInward(dEdge); + let mut gaps = 0; + loop { + // detect an endless loop (lack of progress). if encountered, please report. + if !(line.points().is_empty() || &&self.p != line.points().last().as_ref().unwrap()) { + return Err(Exceptions::IllegalStateException(None)); + } + if !line.points().is_empty() && &&self.p == line.points().last().as_ref().unwrap() { + return Ok(false); + } + // log(p); + + // if we drifted too far outside of the code, break + if line.isValid() + && line.signedDistance(&self.p) < -5.0 + && (!line.evaluate_max_distance(None, None) || line.signedDistance(&self.p) < -5.0) + { + return Ok(false); + } + + // if we are drifting towards the inside of the code, pull the current position back out onto the line + if line.isValid() && line.signedDistance(&self.p) > 3.0 { + // The current direction d and the line we are tracing are supposed to be roughly parallel. + // In case the 'go outward' step in traceStep lead us astray, we might end up with a line + // that is almost perpendicular to d. Then the back-projection below can result in an + // endless loop. Break if the angle between d and line is greater than 45 deg. + if (RXingResultPoint::dot(RXingResultPoint::normalized(self.d), line.normal())) + .abs() + > 0.7 + // thresh is approx. sin(45 deg) + { + return Ok(false); + } + + let mut np = line.project(&self.p); + // make sure we are making progress even when back-projecting: + // consider a 90deg corner, rotated 45deg. we step away perpendicular from the line and get + // back projected where we left off the line. + // The 'while' instead of 'if' was introduced to fix the issue with #245. It turns out that + // np can actually be behind the projection of the last line point and we need 2 steps in d + // to prevent a dead lock. see #245.png + while RXingResultPoint::distance( + np, + line.project(line.points().last().as_ref().unwrap()), + ) < 1.0 + { + np += self.d; + } + self.p = RXingResultPoint::centered(&np); + } else { + let stepLengthInMainDir = if line.points().is_empty() { + 0.0 + } else { + RXingResultPoint::dot( + RXingResultPoint::mainDirection(self.d), + self.p - line.points().last().unwrap(), + ) + }; + line.add(&self.p)?; + + if stepLengthInMainDir > 1.0 { + gaps += 1; + if gaps >= 2 || line.points().len() > 5 { + if !line.evaluate_max_distance(Some(1.5), None) { + return Ok(false); + } + if !self.updateDirectionFromOrigin( + &(self.p - line.project(&self.p) + *line.points().first().unwrap()), + ) { + return Ok(false); + } + // check if the first half of the top-line trace is complete. + // the minimum code size is 10x10 -> every code has at least 4 gaps + //TODO: maybe switch to termination condition based on bottom line length to get a better + // finishLine for the right line trace + if !finishLine.isValid() && gaps == 4 { + // undo the last insert, it will be inserted again after the restart + line.pop_back(); + // gaps -= 1; + return Ok(true); + } + } + } else if gaps == 0 && line.points().len() >= (2 * maxStepSize) as usize { + return Ok(false); + } // no point in following a line that has no gaps + } + + if finishLine.isValid() { + maxStepSize = + std::cmp::min(maxStepSize, (finishLine.signedDistance(&self.p)) as i32); + } + + let stepResult = self.traceStep(dEdge, maxStepSize, line.isValid())?; + + if stepResult != StepResult::Found + // we are successful iff we found an open end across a valid finishLine + { + return Ok(stepResult == StepResult::OpenEnd + && finishLine.isValid() + && (finishLine.signedDistance(&self.p)) as i32 <= maxStepSize + 1); + } + } //while (true); + } + + pub fn traceCorner( + &mut self, + dir: &mut RXingResultPoint, + corner: &mut RXingResultPoint, + ) -> Result { + self.step(None); + // log(p); + *corner = self.p; + std::mem::swap(&mut self.d, dir); + self.traceStep(&(-1.0 * dir), 2, false)?; + // #ifdef PRINT_DEBUG + // printf("turn: %.0f x %.0f -> %.2f, %.2f\n", p.x, p.y, d.x, d.y); + // #endif + Ok(self.isIn(corner) && self.isIn(&self.p)) + } +} diff --git a/src/datamatrix/detector/zxing_cpp_detector/mod.rs b/src/datamatrix/detector/zxing_cpp_detector/mod.rs new file mode 100644 index 0000000..6edbde9 --- /dev/null +++ b/src/datamatrix/detector/zxing_cpp_detector/mod.rs @@ -0,0 +1,20 @@ +mod bitmatrix_cursor; +mod cpp_new_detector; +mod direction; +mod dm_regression_line; +mod edge_tracer; +mod quad; +mod regression_line; +mod step_result; +pub(self) mod util; +mod value; + +pub(self) use 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 quad::*; +pub(self) use regression_line::*; +pub(self) use step_result::*; +pub(self) use value::*; diff --git a/src/datamatrix/detector/zxing_cpp_detector/quad.rs b/src/datamatrix/detector/zxing_cpp_detector/quad.rs new file mode 100644 index 0000000..99dcc2e --- /dev/null +++ b/src/datamatrix/detector/zxing_cpp_detector/quad.rs @@ -0,0 +1,214 @@ +use crate::RXingResultPoint; + +#[derive(Clone, Debug)] +pub struct Quadrilateral([RXingResultPoint; 4]); + +impl Quadrilateral { + // using Base = std::array; + // using Base::at; + // public: + // using Point = T; + + #[allow(dead_code)] + pub fn new() -> Self { + Self([RXingResultPoint { x: 0.0, y: 0.0 }; 4]) + } + // pub fn with_f32( tl:f32, tr:f32, br:f32, bl:f32) -> Self { + // Self([tl, tr,br, bl ]) + // } + + pub fn with_points( + tl: RXingResultPoint, + tr: RXingResultPoint, + br: RXingResultPoint, + bl: RXingResultPoint, + ) -> Self { + Self([tl, tr, br, bl]) + } + + pub fn topLeft(&self) -> &RXingResultPoint { + &self.0[0] + } //const noexcept { return at(0); } + pub fn topRight(&self) -> &RXingResultPoint { + &self.0[1] + } //const noexcept { return at(1); } + pub fn bottomRight(&self) -> &RXingResultPoint { + &self.0[2] + } //const noexcept { return at(2); } + pub fn bottomLeft(&self) -> &RXingResultPoint { + &self.0[3] + } //const noexcept { return at(3); } + + #[allow(dead_code)] + pub fn orientation(&self) -> f64 { + let centerLine = + (*self.topRight() + *self.bottomRight()) - (*self.topLeft() + *self.bottomLeft()); + if (centerLine == RXingResultPoint { x: 0.0, y: 0.0 }) { + return 0.0; + } + let centerLineF = RXingResultPoint::normalized(centerLine); + f32::atan2(centerLineF.y, centerLineF.x).into() + } + pub fn points(&self) -> &[RXingResultPoint] { + &self.0 + } +} + +#[allow(dead_code)] +pub fn Rectangle(width: i32, height: i32, margin: Option) -> Quadrilateral { + let margin = if let Some(m) = margin { m } else { 0 }; + + Quadrilateral([ + RXingResultPoint { + x: margin as f32, + y: margin as f32, + }, + RXingResultPoint { + x: width as f32 - margin as f32, + y: margin as f32, + }, + RXingResultPoint { + x: width as f32 - margin as f32, + y: height as f32 - margin as f32, + }, + RXingResultPoint { + x: margin as f32, + y: height as f32 - margin as f32, + }, + ]) +} + +#[allow(dead_code)] +pub fn CenteredSquare(size: i32) -> Quadrilateral { + Scale( + &Quadrilateral([ + RXingResultPoint { x: -1.0, y: -1.0 }, + RXingResultPoint { x: 1.0, y: -1.0 }, + RXingResultPoint { x: 1.0, y: 1.0 }, + RXingResultPoint { x: -1.0, y: 1.0 }, + ]), + size / 2, + ) +} + +#[allow(dead_code)] +pub fn Line(y: i32, xStart: i32, xStop: i32) -> Quadrilateral { + Quadrilateral([ + RXingResultPoint { + x: xStart as f32, + y: y as f32, + }, + RXingResultPoint { + x: xStop as f32, + y: y as f32, + }, + RXingResultPoint { + x: xStop as f32, + y: y as f32, + }, + RXingResultPoint { + x: xStart as f32, + y: y as f32, + }, + ]) +} + +#[allow(dead_code)] +pub fn IsConvex(poly: &Quadrilateral) -> bool { + let N = poly.0.len(); + let mut sign = false; + + let mut m = f32::INFINITY; + let mut M = 0.0_f32; + + for i in 0..N + // for(int i = 0; i < N; i++) + { + let d1 = poly.0[(i + 2) % N] - poly.0[(i + 1) % N]; + let d2 = poly.0[i] - poly.0[(i + 1) % N]; + let cp = RXingResultPoint::cross(&d1, &d2); + + m = if m.abs() > cp { cp } else { m.abs() }; + + M = if M.abs() > cp { M.abs() } else { cp }; + // m = std::cmp::min((m).abs(), cp); + // M = std::cmp::max((M).abs(), cp); + + if i == 0 { + sign = cp > 0.0; + } else if sign != (cp > 0.0) { + return false; + } + } + + // It turns out being convex is not enough to prevent a "numerical instability" + // that can cause the corners being projected inside the image boundaries but + // some points near the corners being projected outside. This has been observed + // where one corner is almost in line with two others. The M/m ratio is below 2 + // for the complete existing sample set. For very "skewed" QRCodes a value of + // around 3 is realistic. A value of 14 has been observed to trigger the + // instability. + M / m < 4.0 +} + +#[allow(dead_code)] +pub fn Scale(q: &Quadrilateral, factor: i32) -> Quadrilateral { + Quadrilateral([ + q.0[0] * factor as f32, + q.0[1] * factor as f32, + q.0[2] * factor as f32, + q.0[3] * factor as f32, + ]) +} + +#[allow(dead_code)] +pub fn Center(q: &Quadrilateral) -> RXingResultPoint { + let reduced: RXingResultPoint = q.0.iter().sum(); + let size = q.0.len() as f32; + reduced / size + // return Reduce(q) / Size(q); +} + +#[allow(dead_code)] +pub fn RotatedCorners(q: &Quadrilateral, n: Option, mirror: Option) -> Quadrilateral { + let n = if let Some(n) = n { n } else { 1 }; + + let mirror = if let Some(m) = mirror { m } else { false }; + + let mut res = q.clone(); + 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 { + res.0.swap(1, 3); + } + // {std::swap(res[1], res[3]);} + res +} + +#[allow(dead_code)] +pub fn IsInside(p: &RXingResultPoint, q: &Quadrilateral) -> bool { + // Test if p is on the same side (right or left) of all polygon segments + let mut pos = 0; + let mut neg = 0; + for i in 0..q.0.len() + // for (int i = 0; i < Size(q); ++i) + { + if RXingResultPoint::cross(&(*p - q.0[i]), &(q.0[(i + 1) % q.0.len()] - q.0[i])) < 0.0 { + neg += 1; + } else { + pos += 1; + } + // (cross(p - q[i], q[(i + 1) % Size(q)] - q[i]) < 0 ? neg : pos)++; + } + + pos == 0 || neg == 0 +} + +#[allow(dead_code)] +pub fn HaveIntersectingBoundingBoxes(a: &Quadrilateral, b: &Quadrilateral) -> bool { + // TODO: this is only a quick and dirty approximation that works for the trivial standard cases + let x = b.topRight().x < a.topLeft().x || b.topLeft().x > a.topRight().x; + let y = b.bottomLeft().y < a.topLeft().y || b.topLeft().y > a.bottomLeft().y; + + !(x || y) +} diff --git a/src/datamatrix/detector/zxing_cpp_detector/regression_line.rs b/src/datamatrix/detector/zxing_cpp_detector/regression_line.rs new file mode 100644 index 0000000..aa73470 --- /dev/null +++ b/src/datamatrix/detector/zxing_cpp_detector/regression_line.rs @@ -0,0 +1,137 @@ +use crate::{Exceptions, RXingResultPoint}; + +pub trait RegressionLine { + // points: Vec, + // direction_inward: RXingResultPoint, + + // } + // impl RegressionLine { + // std::vector _points; + // PointF _directionInward; + // PointF::value_t a = NAN, b = NAN, c = NAN; + + // fn intersect(&self, l1: &T, l2: &T2) + // -> RXingResultPoint; + + // fn evaluate_begin_end(&self, begin:&RXingResultPoint, end:&RXingResultPoint) -> 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: &[RXingResultPoint]) -> bool; // { return self.evaluate_begin_end(&points.front(), &points.back() + 1); } + fn evaluateSelf(&mut self) -> bool; + + fn distance(&self, a: &RXingResultPoint, b: &RXingResultPoint) -> f32 { + crate::result_point_utils::distance(a, b) + } + + // 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) -> &[RXingResultPoint]; //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) -> RXingResultPoint; //const { return isValid() ? PointF(a, b) : _directionInward; } + fn signedDistance(&self, p: &RXingResultPoint) -> f32; //const { return dot(normal(), p) - c; } + fn distance_single(&self, p: &RXingResultPoint) -> f32; //const { return std::abs(signedDistance(PointF(p))); } + fn project(&self, p: &RXingResultPoint) -> RXingResultPoint { + *p - self.normal() * self.signedDistance(p) + } + + fn reset(&mut self); + // { + // _points.clear(); + // _directionInward = {}; + // a = b = c = NAN; + // } + + fn add(&mut self, p: &RXingResultPoint) -> Result<(), Exceptions>; //{ + // 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: &RXingResultPoint); //{ _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; + // } +} diff --git a/src/datamatrix/detector/zxing_cpp_detector/step_result.rs b/src/datamatrix/detector/zxing_cpp_detector/step_result.rs new file mode 100644 index 0000000..63750d0 --- /dev/null +++ b/src/datamatrix/detector/zxing_cpp_detector/step_result.rs @@ -0,0 +1,6 @@ +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum StepResult { + Found, + OpenEnd, + ClosedEnd, +} diff --git a/src/datamatrix/detector/zxing_cpp_detector/util.rs b/src/datamatrix/detector/zxing_cpp_detector/util.rs new file mode 100644 index 0000000..00e3c01 --- /dev/null +++ b/src/datamatrix/detector/zxing_cpp_detector/util.rs @@ -0,0 +1,45 @@ +use crate::{Exceptions, RXingResultPoint}; + +use super::{DMRegressionLine, Direction, RegressionLine}; + +#[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 { + if !(l1.isValid() && l2.isValid()) { + return Err(Exceptions::IllegalStateException(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; + Ok(RXingResultPoint { x, y }) +} + +#[allow(dead_code)] +#[inline(always)] +pub fn opposite(dir: Direction) -> Direction { + if dir == Direction::Left { + Direction::Right + } else { + Direction::Left + } +} diff --git a/src/datamatrix/detector/zxing_cpp_detector/value.rs b/src/datamatrix/detector/zxing_cpp_detector/value.rs new file mode 100644 index 0000000..5419733 --- /dev/null +++ b/src/datamatrix/detector/zxing_cpp_detector/value.rs @@ -0,0 +1,36 @@ +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Value { + Invalid = -1, + White = 0, + Black = 1, +} +impl Value { + pub fn isBlack(&self) -> bool { + self == &Value::Black + } + pub fn isWhite(&self) -> bool { + self == &Value::White + } + pub fn isValid(&self) -> bool { + self != &Value::Invalid + } +} + +impl From for Value { + fn from(value: bool) -> Self { + match value { + true => Value::Black, + false => Value::White, + } + } +} + +impl From for bool { + fn from(value: Value) -> Self { + match value { + Value::Invalid => false, + Value::White => true, + Value::Black => true, + } + } +} diff --git a/src/rxing_result_point.rs b/src/rxing_result_point.rs index e4df79a..c0f3575 100644 --- a/src/rxing_result_point.rs +++ b/src/rxing_result_point.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{fmt, iter::Sum}; use crate::ResultPoint; use std::hash::Hash; @@ -22,7 +22,7 @@ impl Hash for RXingResultPoint { } impl PartialEq for RXingResultPoint { fn eq(&self, other: &Self) -> bool { - self.x.to_string() == other.x.to_string() && self.y.to_string() == other.y.to_string() + self.x == other.x && self.y == other.y } } impl Eq for RXingResultPoint {} @@ -30,6 +30,26 @@ impl RXingResultPoint { pub const fn new(x: f32, y: f32) -> Self { Self { x, y } } + pub const fn with_single(x: f32) -> Self { + Self { x, y: x } + } +} + +impl std::ops::AddAssign for RXingResultPoint { + fn add_assign(&mut self, rhs: Self) { + self.x = self.x + rhs.x; + self.y = self.y + rhs.y; + } +} + +impl<'a> Sum<&'a RXingResultPoint> for RXingResultPoint { + fn sum>(iter: I) -> Self { + let mut add = RXingResultPoint { x: 0.0, y: 0.0 }; + for n in iter { + add += *n; + } + add + } } impl ResultPoint for RXingResultPoint { @@ -51,3 +71,200 @@ impl fmt::Display for RXingResultPoint { write!(f, "({},{})", self.x, self.y) } } + +impl std::ops::Sub for RXingResultPoint { + type Output = RXingResultPoint; + + fn sub(self, rhs: Self) -> Self::Output { + Self { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } +} + +impl std::ops::Sub<&RXingResultPoint> for RXingResultPoint { + type Output = RXingResultPoint; + + fn sub(self, rhs: &Self) -> Self::Output { + Self { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } +} + +impl std::ops::Neg for RXingResultPoint { + type Output = RXingResultPoint; + + fn neg(self) -> Self::Output { + Self { + x: -self.x, + y: -self.y, + } + } +} + +impl std::ops::Add for RXingResultPoint { + type Output = RXingResultPoint; + + fn add(self, rhs: RXingResultPoint) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +impl std::ops::Mul for RXingResultPoint { + type Output = RXingResultPoint; + + fn mul(self, rhs: RXingResultPoint) -> Self::Output { + Self { + x: self.x * rhs.x, + y: self.y * rhs.y, + } + } +} + +impl std::ops::Mul for RXingResultPoint { + type Output = RXingResultPoint; + + fn mul(self, rhs: f32) -> Self::Output { + Self { + x: self.x * rhs, + y: self.y * rhs, + } + } +} + +impl std::ops::Mul for RXingResultPoint { + type Output = RXingResultPoint; + + fn mul(self, rhs: i32) -> Self::Output { + Self { + x: self.x * rhs as f32, + y: self.y * rhs as f32, + } + } +} + +impl std::ops::Mul for i32 { + type Output = RXingResultPoint; + + fn mul(self, rhs: RXingResultPoint) -> Self::Output { + RXingResultPoint { + x: rhs.x * self as f32, + y: rhs.y * self as f32, + } + } +} + +impl std::ops::Mul for f32 { + type Output = RXingResultPoint; + + fn mul(self, rhs: RXingResultPoint) -> Self::Output { + RXingResultPoint { + x: rhs.x * self, + y: rhs.y * self, + } + } +} + +impl std::ops::Mul<&RXingResultPoint> for f32 { + type Output = RXingResultPoint; + + fn mul(self, rhs: &RXingResultPoint) -> Self::Output { + RXingResultPoint { + x: rhs.x * self, + y: rhs.y * self, + } + } +} + +impl std::ops::Mul<&mut RXingResultPoint> for f32 { + type Output = RXingResultPoint; + + fn mul(self, rhs: &mut RXingResultPoint) -> Self::Output { + RXingResultPoint { + x: rhs.x * self, + y: rhs.y * self, + } + } +} + +impl std::ops::Div for RXingResultPoint { + type Output = RXingResultPoint; + + fn div(self, rhs: f32) -> Self::Output { + Self { + x: self.x / rhs, + y: self.y / rhs, + } + } +} + +impl RXingResultPoint { + pub fn dot(a: RXingResultPoint, b: RXingResultPoint) -> f32 { + a.x * b.x + a.y * b.y + } + + pub fn cross(a: &RXingResultPoint, b: &RXingResultPoint) -> f32 { + a.x * b.y - b.x * a.y + } + + /// L1 norm + pub fn sumAbsComponent(p: &RXingResultPoint) -> f32 { + (p.x).abs() + (p.y).abs() + } + + /// L2 norm + pub fn length(p: RXingResultPoint) -> f32 { + (Self::dot(p, p)).sqrt() + } + + /// L-inf norm + pub fn maxAbsComponent(p: &RXingResultPoint) -> f32 { + let a = (p.x).abs(); + let b = (p.y).abs(); + + if a > b { + a + } else { + b + } + + // return std::cmp::max((p.x).abs(), (p.y).abs()); + } + + pub fn distance(a: RXingResultPoint, b: RXingResultPoint) -> f32 { + Self::length(a - b) + } + + /// Calculate a floating point pixel coordinate representing the 'center' of the pixel. + /// This is sort of the inverse operation of the PointI(PointF) conversion constructor. + /// See also the documentation of the GridSampler API. + #[inline(always)] + pub fn centered(p: &RXingResultPoint) -> RXingResultPoint { + RXingResultPoint { + x: (p.x).floor() + 0.5, + y: (p.y).floor() + 0.5, + } + } + + pub fn normalized(d: RXingResultPoint) -> RXingResultPoint { + d / Self::length(d) + } + + pub fn bresenhamDirection(d: &RXingResultPoint) -> RXingResultPoint { + *d / Self::maxAbsComponent(d) + } + + pub fn mainDirection(d: RXingResultPoint) -> RXingResultPoint { + if (d.x).abs() > (d.y).abs() { + Self::new(d.x, 0.0) + } else { + Self::new(0.0, d.y) + } + } +}