1use anyhow::anyhow;
2use log::{debug, warn};
3use na::{Matrix3, Point2};
4
5type Point2D = (f32, f32);
6
7extern crate nalgebra as na;
8
9const DST_SIZE: f32 = 1.;
13pub const DEFAULT_DST_QUAD: RectCorners = [
14 (0., 0.),
15 (DST_SIZE, 0.),
16 (DST_SIZE, DST_SIZE),
17 (0., DST_SIZE),
18];
19
20pub type RectCorners = [Point2D; 4];
24type Matrix8x8 = na::SMatrix<f32, 8, 8>;
25
26pub struct QuadTransformer {
27 transform_matrix: Option<Matrix3<f32>>,
28 ignore_outside_margin: Option<f32>,
29 dst_quad: Option<RectCorners>,
30}
31
32impl Default for QuadTransformer {
33 fn default() -> Self {
34 Self::new(None, None, None)
35 }
36}
37
38impl QuadTransformer {
39 pub fn new(
40 src_quad: Option<RectCorners>,
41 dst_quad: Option<RectCorners>,
42 ignore_outside_margin: Option<f32>,
43 ) -> QuadTransformer {
44 if ignore_outside_margin.is_none() {
45 warn!("No outside margin value set; points will not be restricted to src_quad");
46 }
47 if let Some(margin) = ignore_outside_margin {
48 warn!("An outside margin value was set; points further than {margin} distance outside of destination quad will be ignored");
49 }
50 let useable_dst_quad: RectCorners = match dst_quad {
51 Some(q) => q,
52 None => DEFAULT_DST_QUAD,
53 };
54 QuadTransformer {
55 transform_matrix: src_quad
56 .map(|quad| build_transform(&quad.clone(), &useable_dst_quad)),
57 dst_quad,
58 ignore_outside_margin,
59 }
60 }
61
62 pub fn set_new_quad(&mut self, src_quad: &RectCorners, dst_quad: Option<RectCorners>) {
63 let useable_dst_quad: RectCorners = match dst_quad {
64 Some(q) => q,
65 None => DEFAULT_DST_QUAD,
66 };
67
68 self.dst_quad = dst_quad;
69
70 self.transform_matrix = Some(build_transform(src_quad, &useable_dst_quad));
71 }
72
73 pub fn transform(&self, point: &Point2D) -> anyhow::Result<Point2D> {
76 match self.transform_matrix {
77 Some(matrix) => {
78 let (x, y) = point;
79 let nalgebra_point = Point2::new(*x, *y);
80
81 let transformed = matrix.transform_point(&nalgebra_point);
82 Ok((transformed.x, transformed.y))
83 }
84 None => Err(anyhow!("No transform matrix")),
85 }
86 }
87
88 pub fn filter_points_inside(&self, points: &[Point2D]) -> Vec<Point2D> {
91 let points: Vec<Point2D> = points
92 .iter()
93 .filter(|point| match self.ignore_outside_margin {
94 Some(_) => self.point_is_inside_quad(point),
95 None => true,
96 })
97 .map(|p| (p.0, p.1))
98 .collect();
99 points
100 }
101
102 pub fn is_ready(&self) -> bool {
103 self.transform_matrix.is_some()
104 }
105
106 pub fn point_is_inside_quad(&self, point: &Point2D) -> bool {
107 let margin = self.ignore_outside_margin.unwrap_or(0.);
108 let (x, y) = point;
109 debug!("...Is {x}, {y} outside of {margin}?");
110 if let Some(dst_quad) = self.dst_quad {
111 let [a, b, _c, d] = dst_quad;
112 *x >= (a.0 - margin)
113 && *x <= (b.0 + margin)
114 && *y >= (a.1 - margin)
115 && *y <= (d.1 + margin)
116 } else {
117 *x >= (0. - margin)
119 && *x <= (DST_SIZE + margin)
120 && *y >= (0. - margin)
121 && *y <= (DST_SIZE + margin)
122 }
123 }
124}
125
126fn build_transform(src_quad: &RectCorners, dst_quad: &RectCorners) -> Matrix3<f32> {
127 let r1: [f32; 8] = [
130 src_quad[0].0,
131 src_quad[0].1,
132 1.,
133 0.,
134 0.,
135 0.,
136 -src_quad[0].0 * dst_quad[0].0,
137 -src_quad[0].1 * dst_quad[0].0,
138 ];
139 let r2: [f32; 8] = [
140 0.,
141 0.,
142 0.,
143 src_quad[0].0,
144 src_quad[0].1,
145 1.,
146 -src_quad[0].0 * dst_quad[0].1,
147 -src_quad[0].1 * dst_quad[0].1,
148 ];
149 let r3: [f32; 8] = [
150 src_quad[1].0,
151 src_quad[1].1,
152 1.,
153 0.,
154 0.,
155 0.,
156 -src_quad[1].0 * dst_quad[1].0,
157 -src_quad[1].1 * dst_quad[1].0,
158 ];
159 let r4: [f32; 8] = [
160 0.,
161 0.,
162 0.,
163 src_quad[1].0,
164 src_quad[1].1,
165 1.,
166 -src_quad[1].0 * dst_quad[1].1,
167 -src_quad[1].1 * dst_quad[1].1,
168 ];
169 let r5: [f32; 8] = [
170 src_quad[2].0,
171 src_quad[2].1,
172 1.,
173 0.,
174 0.,
175 0.,
176 -src_quad[2].0 * dst_quad[2].0,
177 -src_quad[2].1 * dst_quad[2].0,
178 ];
179 let r6: [f32; 8] = [
180 0.,
181 0.,
182 0.,
183 src_quad[2].0,
184 src_quad[2].1,
185 1.,
186 -src_quad[2].0 * dst_quad[2].1,
187 -src_quad[2].1 * dst_quad[2].1,
188 ];
189 let r7: [f32; 8] = [
190 src_quad[3].0,
191 src_quad[3].1,
192 1.,
193 0.,
194 0.,
195 0.,
196 -src_quad[3].0 * dst_quad[3].0,
197 -src_quad[3].1 * dst_quad[3].0,
198 ];
199 let r8: [f32; 8] = [
200 0.,
201 0.,
202 0.,
203 src_quad[3].0,
204 src_quad[3].1,
205 1.,
206 -src_quad[3].0 * dst_quad[3].1,
207 -src_quad[3].1 * dst_quad[3].1,
208 ];
209 let combined = vec![r1, r2, r3, r4, r5, r6, r7, r8].into_iter().flatten();
210
211 let matrix_a = Matrix8x8::from_iterator(combined);
212
213 let dst_quad_elements = vec![
214 dst_quad[0].0,
215 dst_quad[0].1,
216 dst_quad[1].0,
217 dst_quad[1].1,
218 dst_quad[2].0,
219 dst_quad[2].1,
220 dst_quad[3].0,
221 dst_quad[3].1,
222 ]
223 .into_iter();
224
225 let matrix_b: na::SMatrix<f32, 1, 8> = na::SMatrix::from_iterator(dst_quad_elements);
227
228 let coefficients = matrix_b * matrix_a.try_inverse().unwrap();
230 Matrix3::new(
233 coefficients[0],
234 coefficients[1],
235 coefficients[2],
236 coefficients[3],
237 coefficients[4],
238 coefficients[5],
239 coefficients[6],
240 coefficients[7],
241 1.,
242 )
243}
244
245#[cfg(test)]
246mod tests {
247
248 use crate::*;
249
250 use super::RectCorners;
251
252 #[test]
253 #[allow(clippy::excessive_precision)]
254 fn test_get_transform_matrix() {
255 let src_quad = [158., 64., 494., 69., 495., 404., 158., 404.];
258 let src_quad: RectCorners = [
259 (src_quad[0], src_quad[1]),
260 (src_quad[2], src_quad[3]),
261 (src_quad[4], src_quad[5]),
262 (src_quad[6], src_quad[7]),
263 ];
264
265 let dst_quad = [100., 500., 152., 564., 148., 604., 100., 560.];
266 let dst_quad: RectCorners = [
267 (dst_quad[0], dst_quad[1]),
268 (dst_quad[2], dst_quad[3]),
269 (dst_quad[4], dst_quad[5]),
270 (dst_quad[6], dst_quad[7]),
271 ];
272
273 let transform_matrix = build_transform(&src_quad, &dst_quad);
274
275 let src_point = (250., 120.);
276
277 let result = {
278 let (x, y) = (src_point.0, src_point.1);
279 let nalgebra_point = nalgebra::Point2::new(x, y);
280
281 let transformed = transform_matrix.transform_point(&nalgebra_point);
282 (transformed.x, transformed.y)
283 };
284 assert_eq!(
285 (result.0.round(), result.1.round()),
286 (
287 117.27521125839255_f32.round(),
288 530.9202410878403_f32.round(),
289 ),
290 );
291 }
292
293 #[test]
294 fn test_get_transform_matrix_simple() {
295 let src_quad: RectCorners = [(0., 0.), (1., 0.), (1., 1.), (0., 1.)];
298 let dst_quad: RectCorners = [(1., 2.), (1., 4.), (3., 4.), (3., 2.)];
299
300 let transform_matrix = build_transform(&src_quad, &dst_quad);
301
302 let src_point = (0.5, 0.5);
310
311 let result = {
312 let (x, y) = (src_point.0, src_point.1);
313 let nalgebra_point = nalgebra::Point2::new(x, y);
314
315 let transformed = transform_matrix.transform_point(&nalgebra_point);
316 (transformed.x, transformed.y)
317 };
318
319 assert_eq!(result, (2., 3.));
320 }
321
322 #[test]
323 fn test_inside_standard_quad() {
324 let point: Point2D = (0.5, 0.5);
325
326 let t = QuadTransformer::default();
327 assert!(t.point_is_inside_quad(&point));
330
331 let point: Point2D = (-0.5, 0.5);
333 assert!(!t.point_is_inside_quad(&point));
334
335 let point: Point2D = (1.0, 1.0);
337 assert!(t.point_is_inside_quad(&point));
338 }
339
340 #[test]
341 fn test_inside_dst_quad() {
342 let centered_dst_quad: RectCorners =
343 [(-100., -100.), (100., -100.), (100., 100.), (-100., 100.)];
344
345 let t = QuadTransformer::new(None, Some(centered_dst_quad), None);
346
347 let point: Point2D = (0., 0.);
349 assert!(t.point_is_inside_quad(&point));
350
351 let point: Point2D = (101., 0.);
353 assert!(!t.point_is_inside_quad(&point));
354
355 let point: Point2D = (100., 0.);
357 assert!(t.point_is_inside_quad(&point));
358 }
359
360 #[test]
361 fn test_inside_with_margin_standard_quad() {
362 let t = QuadTransformer::new(None, None, Some(0.25));
363
364 let point: Point2D = (1.1, 0.);
366 assert!(t.point_is_inside_quad(&point));
367
368 let point: Point2D = (1.5, 0.);
370 assert!(!t.point_is_inside_quad(&point));
371 }
372
373 #[test]
374 fn test_inside_with_margin_dst_quad() {
375 let centered_dst_quad: RectCorners =
376 [(-100., -100.), (100., -100.), (100., 100.), (-100., 100.)];
377
378 let t = QuadTransformer::new(None, Some(centered_dst_quad), Some(10.0));
379
380 let point: Point2D = (101., 0.);
382 assert!(t.point_is_inside_quad(&point));
383
384 let point: Point2D = (115., 0.);
386 assert!(!t.point_is_inside_quad(&point));
387 }
388}