From 2b7d053646672a37b0e98f2375a8beb657a13303 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Wed, 8 Mar 2023 11:17:43 -0600 Subject: [PATCH] Promote `Quadrilateral` to default The library now uses the `Quadrilateral` struct when handling sets of four points in `GridSampler` and `PerspectiveTransform` --- src/aztec/detector.rs | 36 +-- src/common/PerspectiveTransformTestCase.rs | 15 +- src/common/default_grid_sampler.rs | 31 +-- src/common/grid_sampler.rs | 38 ++- src/common/mod.rs | 3 + src/common/perspective_transform.rs | 116 +++++----- src/common/quad.rs | 217 ++++++++++++++++++ .../detector/datamatrix_detector.rs | 22 +- .../zxing_cpp_detector/cpp_new_detector.rs | 25 +- .../detector/zxing_cpp_detector/mod.rs | 2 - .../detector/zxing_cpp_detector/quad.rs | 209 ----------------- src/maxicode/detector.rs | 24 +- src/qrcode/detector/qrcode_detector.rs | 32 +-- 13 files changed, 348 insertions(+), 422 deletions(-) create mode 100644 src/common/quad.rs delete mode 100644 src/datamatrix/detector/zxing_cpp_detector/quad.rs diff --git a/src/aztec/detector.rs b/src/aztec/detector.rs index 206a8b4..145d817 100644 --- a/src/aztec/detector.rs +++ b/src/aztec/detector.rs @@ -18,7 +18,7 @@ use crate::{ common::{ detector::WhiteRectangleDetector, reedsolomon::{self, ReedSolomonDecoder}, - BitMatrix, DefaultGridSampler, GridSampler, Result, + BitMatrix, DefaultGridSampler, GridSampler, Result, Quadrilateral, }, exceptions::Exceptions, point, Point, @@ -89,13 +89,15 @@ impl<'a> Detector<'_> { // 3. Get the size of the matrix and other parameters from the bull's eye self.extractParameters(&bulls_eye_corners)?; +let src_quad = Quadrilateral::new(bulls_eye_corners[self.shift as usize % 4], + bulls_eye_corners[(self.shift as usize + 1) % 4], + bulls_eye_corners[(self.shift as usize + 2) % 4], + bulls_eye_corners[(self.shift as usize + 3) % 4],); + // 4. Sample the grid let bits = self.sample_grid( self.image, - bulls_eye_corners[self.shift as usize % 4], - bulls_eye_corners[(self.shift as usize + 1) % 4], - bulls_eye_corners[(self.shift as usize + 2) % 4], - bulls_eye_corners[(self.shift as usize + 3) % 4], + src_quad )?; // 5. Get the corners of the matrix. @@ -457,10 +459,7 @@ impl<'a> Detector<'_> { fn sample_grid( &self, image: &BitMatrix, - top_left: Point, - top_right: Point, - bottom_right: Point, - bottom_left: Point, + quad: Quadrilateral, ) -> Result { let sampler = DefaultGridSampler::default(); let dimension = self.get_dimension(); @@ -468,26 +467,13 @@ impl<'a> Detector<'_> { let low = dimension as f32 / 2.0 - self.nb_center_layers as f32; let high = dimension as f32 / 2.0 + self.nb_center_layers as f32; + let dst = Quadrilateral::new(point(low,low), point(high,low), point(high,high), point(low,high)); + sampler.sample_grid_detailed( image, dimension, dimension, - low, - low, // topleft - high, - low, // topright - high, - high, // bottomright - low, - high, // bottomleft - top_left.x, - top_left.y, - top_right.x, - top_right.y, - bottom_right.x, - bottom_right.y, - bottom_left.x, - bottom_left.y, + dst, quad ) } diff --git a/src/common/PerspectiveTransformTestCase.rs b/src/common/PerspectiveTransformTestCase.rs index 8d875ae..c804d53 100644 --- a/src/common/PerspectiveTransformTestCase.rs +++ b/src/common/PerspectiveTransformTestCase.rs @@ -19,17 +19,20 @@ // import org.junit.Assert; // import org.junit.Test; +use crate::point; + /** * @author Sean Owen */ // public final class PerspectiveTransformTestCase extends Assert { -use super::PerspectiveTransform; +use super::{PerspectiveTransform, Quadrilateral}; static EPSILON: f32 = 1.0E-4f32; #[test] fn test_square_to_quadrilateral() { - let pt = PerspectiveTransform::squareToQuadrilateral(2.0, 3.0, 10.0, 4.0, 16.0, 15.0, 4.0, 9.0); + let square = Quadrilateral::new(point(2.0, 3.0), point(10.0, 3.0), point(16.0, 15.0), point(4.0,9.0)); + let pt = PerspectiveTransform::squareToQuadrilateral(square); assert_point_equals(2.0, 3.0, 0.0, 0.0, &pt); assert_point_equals(10.0, 4.0, 1.0, 0.0, &pt); assert_point_equals(4.0, 9.0, 0.0, 1.0, &pt); @@ -40,10 +43,10 @@ fn test_square_to_quadrilateral() { #[test] fn test_quadrilateral_to_quadrilateral() { - let pt = PerspectiveTransform::quadrilateralToQuadrilateral( - 2.0, 3.0, 10.0, 4.0, 16.0, 15.0, 4.0, 9.0, 103.0, 110.0, 300.0, 120.0, 290.0, 270.0, 150.0, - 280.0, - ); + let quad1 = Quadrilateral::new(point(2.0, 3.0), point(10.0, 4.0), point(16.0, 15.0), point(4.0, 9.0)); + let quad2 = Quadrilateral::new(point(103.0, 110.0), point(300.0, 120.0), point(290.0, 270.0), point(150.0,280.0)); + let pt = PerspectiveTransform::quadrilateralToQuadrilateral(quad1, quad2); + assert_point_equals(103.0, 110.0, 2.0, 3.0, &pt); assert_point_equals(300.0, 120.0, 10.0, 4.0, &pt); assert_point_equals(290.0, 270.0, 16.0, 15.0, &pt); diff --git a/src/common/default_grid_sampler.rs b/src/common/default_grid_sampler.rs index 55e9d4b..a2855b3 100644 --- a/src/common/default_grid_sampler.rs +++ b/src/common/default_grid_sampler.rs @@ -18,10 +18,10 @@ // import com.google.zxing.NotFoundException; -use crate::common::Result; +use crate::{common::Result}; use crate::Exceptions; -use super::{BitMatrix, GridSampler, PerspectiveTransform}; +use super::{BitMatrix, GridSampler, PerspectiveTransform, Quadrilateral, SamplerControl}; /** * @author Sean Owen @@ -35,29 +35,14 @@ impl GridSampler for DefaultGridSampler { image: &BitMatrix, dimensionX: u32, dimensionY: u32, - p1ToX: f32, - p1ToY: f32, - p2ToX: f32, - p2ToY: f32, - p3ToX: f32, - p3ToY: f32, - p4ToX: f32, - p4ToY: f32, - p1FromX: f32, - p1FromY: f32, - p2FromX: f32, - p2FromY: f32, - p3FromX: f32, - p3FromY: f32, - p4FromX: f32, - p4FromY: f32, + dst: Quadrilateral, + src:Quadrilateral ) -> Result { let transform = PerspectiveTransform::quadrilateralToQuadrilateral( - p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, - p2FromY, p3FromX, p3FromY, p4FromX, p4FromY, + dst, src, ); - self.sample_grid(image, dimensionX, dimensionY, &transform) + self.sample_grid(image, dimensionX, dimensionY, &[SamplerControl::new(dimensionX, dimensionY, transform)]) } fn sample_grid( @@ -65,7 +50,7 @@ impl GridSampler for DefaultGridSampler { image: &BitMatrix, dimensionX: u32, dimensionY: u32, - transform: &PerspectiveTransform, + controls: &[SamplerControl] ) -> Result { if dimensionX == 0 || dimensionY == 0 { return Err(Exceptions::NOT_FOUND); @@ -83,7 +68,7 @@ impl GridSampler for DefaultGridSampler { points[x + 1] = i_value; x += 2; } - transform.transform_points_single(&mut points); + controls.first().unwrap().transform.transform_points_single(&mut points); // Quick check to see if points transformed to something inside the image; // sufficient to check the endpoints self.checkAndNudgePoints(image, &mut points)?; diff --git a/src/common/grid_sampler.rs b/src/common/grid_sampler.rs index 6477f1f..aeea7c2 100644 --- a/src/common/grid_sampler.rs +++ b/src/common/grid_sampler.rs @@ -18,10 +18,10 @@ // import com.google.zxing.NotFoundException; -use crate::common::Result; -use crate::Exceptions; +use crate::{common::Result, Point}; +use crate::{Exceptions, point}; -use super::{BitMatrix, PerspectiveTransform}; +use super::{BitMatrix, PerspectiveTransform, Quadrilateral}; /** * Implementations of this class can, given locations of finder patterns for a QR code in an @@ -93,22 +93,8 @@ pub trait GridSampler { image: &BitMatrix, dimensionX: u32, dimensionY: u32, - p1ToX: f32, - p1ToY: f32, - p2ToX: f32, - p2ToY: f32, - p3ToX: f32, - p3ToY: f32, - p4ToX: f32, - p4ToY: f32, - p1FromX: f32, - p1FromY: f32, - p2FromX: f32, - p2FromY: f32, - p3FromX: f32, - p3FromY: f32, - p4FromX: f32, - p4FromY: f32, + dst: Quadrilateral, + src:Quadrilateral ) -> Result; fn sample_grid( @@ -116,7 +102,7 @@ pub trait GridSampler { image: &BitMatrix, dimensionX: u32, dimensionY: u32, - transform: &PerspectiveTransform, + controls: &[SamplerControl] ) -> Result; /** @@ -195,3 +181,15 @@ pub trait GridSampler { Ok(()) } } + +pub struct SamplerControl { + pub p0: Point, + pub p1: Point, + pub transform: PerspectiveTransform +} + +impl SamplerControl { + pub fn new( width: u32, height: u32, transform: PerspectiveTransform ) -> Self { + Self{ p0: point(0.0, 0.0), p1: point(width as f32, height as f32), transform } + } +} \ No newline at end of file diff --git a/src/common/mod.rs b/src/common/mod.rs index 6a829d1..8327876 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -109,6 +109,9 @@ pub use hybrid_binarizer::*; mod eci; pub use eci::*; +mod quad; +pub use quad::*; + #[cfg(feature = "otsu_level")] mod otsu_level_binarizer; #[cfg(feature = "otsu_level")] diff --git a/src/common/perspective_transform.rs b/src/common/perspective_transform.rs index 2c49565..a7038f2 100644 --- a/src/common/perspective_transform.rs +++ b/src/common/perspective_transform.rs @@ -16,6 +16,12 @@ // package com.google.zxing.common; +use std::ops::Mul; + +use crate::point; + +use super::Quadrilateral; + /** *

This class implements a perspective transform in two dimensions. Given four source and four * destination points, it will compute the transformation implied between them. The code is based @@ -63,27 +69,18 @@ impl PerspectiveTransform { #[allow(clippy::too_many_arguments)] pub fn quadrilateralToQuadrilateral( - x0: f32, - y0: f32, - x1: f32, - y1: f32, - x2: f32, - y2: f32, - x3: f32, - y3: f32, - x0p: f32, - y0p: f32, - x1p: f32, - y1p: f32, - x2p: f32, - y2p: f32, - x3p: f32, - y3p: f32, + dst: Quadrilateral, + src: Quadrilateral ) -> Self { - let q_to_s = PerspectiveTransform::quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3); + + // let q_to_s = PerspectiveTransform::quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3); + // let s_to_q = + // PerspectiveTransform::squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); + + let q_to_s = PerspectiveTransform::quadrilateralToSquare(dst); let s_to_q = - PerspectiveTransform::squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); - s_to_q.times(&q_to_s) + PerspectiveTransform::squareToQuadrilateral(src); + s_to_q * q_to_s } pub fn transform_points_single(&self, points: &mut [f32]) { @@ -122,35 +119,29 @@ impl PerspectiveTransform { #[allow(clippy::too_many_arguments)] pub fn squareToQuadrilateral( - x0: f32, - y0: f32, - x1: f32, - y1: f32, - x2: f32, - y2: f32, - x3: f32, - y3: f32, + square: Quadrilateral ) -> Self { - let dx3 = x0 - x1 + x2 - x3; - let dy3 = y0 - y1 + y2 - y3; - if dx3 == 0.0 && dy3 == 0.0 { + let [p0, p1, p2, p3 ] = square.0; + + let d3 = p0 - p1 + p2 - p3; + if d3 == point(0.0,0.0) { // Affine - PerspectiveTransform::new(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0.0, 0.0, 1.0) + PerspectiveTransform::new(p1.x - p0.x, p2.x - p1.x, p0.x, p1.y - p0.y, p2.y - p1.y, p0.y, 0.0, 0.0, 1.0) } else { - let dx1 = x1 - x2; - let dx2 = x3 - x2; - let dy1 = y1 - y2; - let dy2 = y3 - y2; - let denominator = dx1 * dy2 - dx2 * dy1; - let a13 = (dx3 * dy2 - dx2 * dy3) / denominator; - let a23 = (dx1 * dy3 - dx3 * dy1) / denominator; + +let d1 = p1 - p2; +let d2 = p3 - p2; + + let denominator = d1.cross(d2); + let a13 = (d3.x * d2.y - d2.x * d3.y) / denominator; + let a23 = (d1.x * d3.y - d3.x * d1.y) / denominator; PerspectiveTransform::new( - x1 - x0 + a13 * x1, - x3 - x0 + a23 * x3, - x0, - y1 - y0 + a13 * y1, - y3 - y0 + a23 * y3, - y0, + p1.x - p0.x + a13 * p1.x, + p3.x - p0.x + a23 * p3.x, + p0.x, + p1.y - p0.y + a13 * p1.y, + p3.y - p0.y + a23 * p3.y, + p0.y, a13, a23, 1.0, @@ -160,17 +151,10 @@ impl PerspectiveTransform { #[allow(clippy::too_many_arguments)] pub fn quadrilateralToSquare( - x0: f32, - y0: f32, - x1: f32, - y1: f32, - x2: f32, - y2: f32, - x3: f32, - y3: f32, + quad: Quadrilateral ) -> Self { // Here, the adjoint serves as the inverse - PerspectiveTransform::squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint() + PerspectiveTransform::squareToQuadrilateral(quad).buildAdjoint() } fn buildAdjoint(&self) -> Self { @@ -187,18 +171,22 @@ impl PerspectiveTransform { self.a11 * self.a22 - self.a12 * self.a21, ) } +} - fn times(&self, other: &Self) -> Self { +impl Mul for PerspectiveTransform { + type Output = PerspectiveTransform; + + fn mul(self, rhs: Self) -> Self::Output { PerspectiveTransform::new( - self.a11 * other.a11 + self.a21 * other.a12 + self.a31 * other.a13, - self.a11 * other.a21 + self.a21 * other.a22 + self.a31 * other.a23, - self.a11 * other.a31 + self.a21 * other.a32 + self.a31 * other.a33, - self.a12 * other.a11 + self.a22 * other.a12 + self.a32 * other.a13, - self.a12 * other.a21 + self.a22 * other.a22 + self.a32 * other.a23, - self.a12 * other.a31 + self.a22 * other.a32 + self.a32 * other.a33, - self.a13 * other.a11 + self.a23 * other.a12 + self.a33 * other.a13, - self.a13 * other.a21 + self.a23 * other.a22 + self.a33 * other.a23, - self.a13 * other.a31 + self.a23 * other.a32 + self.a33 * other.a33, + self.a11 * rhs.a11 + self.a21 * rhs.a12 + self.a31 * rhs.a13, + self.a11 * rhs.a21 + self.a21 * rhs.a22 + self.a31 * rhs.a23, + self.a11 * rhs.a31 + self.a21 * rhs.a32 + self.a31 * rhs.a33, + self.a12 * rhs.a11 + self.a22 * rhs.a12 + self.a32 * rhs.a13, + self.a12 * rhs.a21 + self.a22 * rhs.a22 + self.a32 * rhs.a23, + self.a12 * rhs.a31 + self.a22 * rhs.a32 + self.a32 * rhs.a33, + self.a13 * rhs.a11 + self.a23 * rhs.a12 + self.a33 * rhs.a13, + self.a13 * rhs.a21 + self.a23 * rhs.a22 + self.a33 * rhs.a23, + self.a13 * rhs.a31 + self.a23 * rhs.a32 + self.a33 * rhs.a33, ) } -} +} \ No newline at end of file diff --git a/src/common/quad.rs b/src/common/quad.rs new file mode 100644 index 0000000..5b8c4cf --- /dev/null +++ b/src/common/quad.rs @@ -0,0 +1,217 @@ +use crate::Point; + +#[derive(Clone, Copy, Debug)] +pub struct Quadrilateral(pub [Point; 4]); + +impl Quadrilateral { + // using Base = std::array; + // using Base::at; + // public: + // using Point = T; + + #[allow(dead_code)] + pub fn new(tl:Point, tr: Point, br: Point, bl: Point) -> Self { + Self([tl,tr,br,bl]) + } + // pub fn with_f32( tl:f32, tr:f32, br:f32, bl:f32) -> Self { + // Self([tl, tr,br, bl ]) + // } + + pub fn with_points(tl: Point, tr: Point, br: Point, bl: Point) -> Self { + Self([tl, tr, br, bl]) + } + + pub fn top_left(&self) -> &Point { + &self.0[0] + } //const noexcept { return at(0); } + pub fn top_right(&self) -> &Point { + &self.0[1] + } //const noexcept { return at(1); } + pub fn bottom_right(&self) -> &Point { + &self.0[2] + } //const noexcept { return at(2); } + pub fn bottom_left(&self) -> &Point { + &self.0[3] + } //const noexcept { return at(3); } + + #[allow(dead_code)] + pub fn orientation(&self) -> f64 { + let centerLine = + (*self.top_right() + *self.bottom_right()) - (*self.top_left() + *self.bottom_left()); + if (centerLine == Point { x: 0.0, y: 0.0 }) { + return 0.0; + } + let centerLineF = Point::normalized(centerLine); + f32::atan2(centerLineF.y, centerLineF.x).into() + } + pub fn points(&self) -> &[Point] { + &self.0 + } +} + +impl Quadrilateral { + #[allow(dead_code)] + pub fn rectangle(width: i32, height: i32, margin: Option) -> Quadrilateral { + let margin = margin.unwrap_or(0); + + Quadrilateral([ + Point { + x: margin as f32, + y: margin as f32, + }, + Point { + x: width as f32 - margin as f32, + y: margin as f32, + }, + Point { + x: width as f32 - margin as f32, + y: height as f32 - margin as f32, + }, + Point { + x: margin as f32, + y: height as f32 - margin as f32, + }, + ]) + } + + #[allow(dead_code)] + pub fn centered_square(size: i32) -> Quadrilateral { + Self::scale( + &Quadrilateral([ + Point { x: -1.0, y: -1.0 }, + Point { x: 1.0, y: -1.0 }, + Point { x: 1.0, y: 1.0 }, + Point { x: -1.0, y: 1.0 }, + ]), + size / 2, + ) + } + + #[allow(dead_code)] + pub fn line(y: i32, xStart: i32, xStop: i32) -> Quadrilateral { + Quadrilateral([ + Point { + x: xStart as f32, + y: y as f32, + }, + Point { + x: xStop as f32, + y: y as f32, + }, + Point { + x: xStop as f32, + y: y as f32, + }, + Point { + x: xStart as f32, + y: y as f32, + }, + ]) + } + + #[allow(dead_code)] + pub fn is_convex(&self) -> bool { + let N = self.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 = self.0[(i + 2) % N] - self.0[(i + 1) % N]; + let d2 = self.0[i] - self.0[(i + 1) % N]; + let cp = d1.cross(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(&self, factor: i32) -> Quadrilateral { + Quadrilateral([ + self.0[0] * factor as f32, + self.0[1] * factor as f32, + self.0[2] * factor as f32, + self.0[3] * factor as f32, + ]) + } + + #[allow(dead_code)] + pub fn center(&self) -> Point { + let reduced: Point = self.0.iter().sum(); + let size = self.0.len() as f32; + reduced / size + // return Reduce(q) / Size(q); + } + + #[allow(dead_code)] + pub fn rotated_corners(&self, 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 = self.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 is_inside(&self, p: Point) -> 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..self.0.len() + // for (int i = 0; i < Size(q); ++i) + { + if Point::cross(p - self.0[i], self.0[(i + 1) % self.0.len()] - self.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 have_intersecting_bounding_boxes(&self, b: &Quadrilateral) -> bool { + // TODO: this is only a quick and dirty approximation that works for the trivial standard cases + let x = b.top_right().x < self.top_left().x || b.top_left().x > self.top_right().x; + let y = b.bottom_left().y < self.top_left().y || b.top_left().y > self.bottom_left().y; + + !(x || y) + } +} + +impl Default for Quadrilateral { + fn default() -> Self { + Self([Point { x: 0.0, y: 0.0 }; 4]) + } +} \ No newline at end of file diff --git a/src/datamatrix/detector/datamatrix_detector.rs b/src/datamatrix/detector/datamatrix_detector.rs index d6be4fa..9e34682 100644 --- a/src/datamatrix/detector/datamatrix_detector.rs +++ b/src/datamatrix/detector/datamatrix_detector.rs @@ -16,7 +16,7 @@ use crate::{ common::{ - detector::WhiteRectangleDetector, BitMatrix, DefaultGridSampler, GridSampler, Result, + detector::WhiteRectangleDetector, BitMatrix, DefaultGridSampler, GridSampler, Result, Quadrilateral, }, point, Exceptions, Point, }; @@ -334,26 +334,14 @@ impl<'a> Detector<'_> { ) -> Result { let sampler = DefaultGridSampler::default(); + let dst = Quadrilateral::new(point(0.5, 0.5), point(dimensionX as f32 - 0.5, 0.5), point(dimensionX as f32 - 0.5,dimensionY as f32 - 0.5), point(0.5, dimensionY as f32 - 0.5)); + let src = Quadrilateral::new(topRight, topLeft,bottomRight, bottomLeft); + sampler.sample_grid_detailed( image, dimensionX, dimensionY, - 0.5, - 0.5, - dimensionX as f32 - 0.5, - 0.5, - dimensionX as f32 - 0.5, - dimensionY as f32 - 0.5, - 0.5, - dimensionY as f32 - 0.5, - topLeft.x, - topLeft.y, - topRight.x, - topRight.y, - bottomRight.x, - bottomRight.y, - bottomLeft.x, - bottomLeft.y, + dst, src ) } 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 432161d..958e1f1 100644 --- a/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs +++ b/src/datamatrix/detector/zxing_cpp_detector/cpp_new_detector.rs @@ -14,13 +14,13 @@ macro_rules! CHECK { use std::{cell::RefCell, rc::Rc}; use crate::{ - common::{BitMatrix, DefaultGridSampler, GridSampler, Result}, + common::{BitMatrix, DefaultGridSampler, GridSampler, Result, Quadrilateral}, datamatrix::detector::{ - zxing_cpp_detector::{util::intersect, BitMatrixCursor, Quadrilateral, RegressionLine}, + zxing_cpp_detector::{util::intersect, BitMatrixCursor, RegressionLine}, DatamatrixDetectorResult, }, qrcode::encoder::ByteMatrix, - Exceptions, Point, + Exceptions, Point, point, }; use super::{DMRegressionLine, EdgeTracer}; @@ -212,27 +212,14 @@ fn Scan( 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 dst = Quadrilateral::new(point(0.0,0.0), point(dimT as f32, 0.0), point(dimT as f32, dimR as f32), point(0.0, dimR as f32)); +let src = sourcePoints; 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().x, - sourcePoints.topLeft().y, - sourcePoints.topRight().x, - sourcePoints.topRight().y, - sourcePoints.bottomRight().x, - sourcePoints.bottomRight().y, - sourcePoints.bottomLeft().x, - sourcePoints.bottomLeft().y, + dst, src ); // let res = grid_sampler.sample_grid(startTracer.img, dimT as u32, dimR as u32, &transform); diff --git a/src/datamatrix/detector/zxing_cpp_detector/mod.rs b/src/datamatrix/detector/zxing_cpp_detector/mod.rs index 6edbde9..1842d6d 100644 --- a/src/datamatrix/detector/zxing_cpp_detector/mod.rs +++ b/src/datamatrix/detector/zxing_cpp_detector/mod.rs @@ -3,7 +3,6 @@ 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; @@ -14,7 +13,6 @@ 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 deleted file mode 100644 index 7e5578e..0000000 --- a/src/datamatrix/detector/zxing_cpp_detector/quad.rs +++ /dev/null @@ -1,209 +0,0 @@ -use crate::Point; - -#[derive(Clone, Debug)] -pub struct Quadrilateral([Point; 4]); - -impl Quadrilateral { - // using Base = std::array; - // using Base::at; - // public: - // using Point = T; - - #[allow(dead_code)] - pub fn new() -> Self { - Self([Point { 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: Point, tr: Point, br: Point, bl: Point) -> Self { - Self([tl, tr, br, bl]) - } - - pub fn topLeft(&self) -> &Point { - &self.0[0] - } //const noexcept { return at(0); } - pub fn topRight(&self) -> &Point { - &self.0[1] - } //const noexcept { return at(1); } - pub fn bottomRight(&self) -> &Point { - &self.0[2] - } //const noexcept { return at(2); } - pub fn bottomLeft(&self) -> &Point { - &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 == Point { x: 0.0, y: 0.0 }) { - return 0.0; - } - let centerLineF = Point::normalized(centerLine); - f32::atan2(centerLineF.y, centerLineF.x).into() - } - pub fn points(&self) -> &[Point] { - &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([ - Point { - x: margin as f32, - y: margin as f32, - }, - Point { - x: width as f32 - margin as f32, - y: margin as f32, - }, - Point { - x: width as f32 - margin as f32, - y: height as f32 - margin as f32, - }, - Point { - x: margin as f32, - y: height as f32 - margin as f32, - }, - ]) -} - -#[allow(dead_code)] -pub fn CenteredSquare(size: i32) -> Quadrilateral { - Scale( - &Quadrilateral([ - Point { x: -1.0, y: -1.0 }, - Point { x: 1.0, y: -1.0 }, - Point { x: 1.0, y: 1.0 }, - Point { x: -1.0, y: 1.0 }, - ]), - size / 2, - ) -} - -#[allow(dead_code)] -pub fn Line(y: i32, xStart: i32, xStop: i32) -> Quadrilateral { - Quadrilateral([ - Point { - x: xStart as f32, - y: y as f32, - }, - Point { - x: xStop as f32, - y: y as f32, - }, - Point { - x: xStop as f32, - y: y as f32, - }, - Point { - 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 = d1.cross(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) -> Point { - let reduced: Point = 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: Point, 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 Point::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/maxicode/detector.rs b/src/maxicode/detector.rs index bd62c77..c03373c 100644 --- a/src/maxicode/detector.rs +++ b/src/maxicode/detector.rs @@ -2,7 +2,7 @@ use num::integer::Roots; use crate::{ - common::{BitMatrix, DefaultGridSampler, DetectorRXingResult, GridSampler, Result}, + common::{BitMatrix, DefaultGridSampler, DetectorRXingResult, GridSampler, Result, Quadrilateral}, point, Exceptions, Point, }; @@ -346,26 +346,16 @@ pub fn detect(image: &BitMatrix, try_harder: bool) -> Result Detector<'_> { ) .ok_or(Exceptions::NOT_FOUND)?; - let bits = Detector::sampleGrid(self.image, &transform, dimension)?; + let bits = Detector::sampleGrid(self.image, transform, dimension)?; let mut points = vec![ Point::from(bottomLeft), @@ -193,33 +193,25 @@ impl<'a> Detector<'_> { sourceBottomRightY = dimMinusThree; } + let dst = Quadrilateral::new(point(3.5, + 3.5), point(dimMinusThree, + 3.5), point(sourceBottomRightX, + sourceBottomRightY), point(3.5, + dimMinusThree)); + let src = Quadrilateral::new( topLeft, topRight, point(bottomRightX, bottomRightY), bottomLeft); + Some(PerspectiveTransform::quadrilateralToQuadrilateral( - 3.5, - 3.5, - dimMinusThree, - 3.5, - sourceBottomRightX, - sourceBottomRightY, - 3.5, - dimMinusThree, - topLeft.x, - topLeft.y, - topRight.x, - topRight.y, - bottomRightX, - bottomRightY, - bottomLeft.x, - bottomLeft.y, + dst, src )) } fn sampleGrid( image: &BitMatrix, - transform: &PerspectiveTransform, + transform: PerspectiveTransform, dimension: u32, ) -> Result { let sampler = DefaultGridSampler::default(); - sampler.sample_grid(image, dimension, dimension, transform) + sampler.sample_grid(image, dimension, dimension, &[SamplerControl::new(dimension, dimension, transform)]) } /**