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

Checks a set of points that have been transformed to sample points on an image against diff --git a/src/common/perspective_transform.rs b/src/common/perspective_transform.rs index 5e42be3..cad843d 100644 --- a/src/common/perspective_transform.rs +++ b/src/common/perspective_transform.rs @@ -81,15 +81,28 @@ impl PerspectiveTransform { Ok(s_to_q * q_to_s) } + pub fn transform_point(&self, point: Point) -> Point { + let x = point.x; + let y = point.y; + let denominator = self.a13 * x + self.a23 * y + self.a33; + Point::new( + (self.a11 * x + self.a21 * y + self.a31) / denominator, + (self.a12 * x + self.a22 * y + self.a32) / denominator, + ) + } + pub fn transform_points_single(&self, points: &mut [Point]) { for point in points.iter_mut() { - // for (int i = 0; i < maxI; i += 2) { - let x = point.x; - let y = point.y; - let denominator = self.a13 * x + self.a23 * y + self.a33; - point.x = (self.a11 * x + self.a21 * y + self.a31) / denominator; - point.y = (self.a12 * x + self.a22 * y + self.a32) / denominator; + *point = self.transform_point(Point::new(point.x, point.y)); } + // for point in points.iter_mut() { + // // for (int i = 0; i < maxI; i += 2) { + // let x = point.x; + // let y = point.y; + // let denominator = self.a13 * x + self.a23 * y + self.a33; + // point.x = (self.a11 * x + self.a21 * y + self.a31) / denominator; + // point.y = (self.a12 * x + self.a22 * y + self.a32) / denominator; + // } } pub fn transform_points_double(&self, x_values: &mut [f32], y_valuess: &mut [f32]) { diff --git a/src/rxing_result_point.rs b/src/rxing_result_point.rs index 1e8d28b..78695c7 100644 --- a/src/rxing_result_point.rs +++ b/src/rxing_result_point.rs @@ -260,6 +260,12 @@ impl From<(i32, i32)> for Point { } } +impl From<(u32, u32)> for Point { + fn from(value: (u32, u32)) -> Self { + Self::new(value.0 as f32, value.1 as f32) + } +} + #[cfg(test)] mod tests { use super::Point;