1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use crate::PointF64;

use super::Matrix;

/// A perspective transform can easily be used to map one 2D quadrilateral to another, 
/// given the corner coordinates for the source and destination quadrilaterals.
///
/// Adapted from https://github.com/jlouthan/perspective-transform
pub struct PerspectiveTransform {
    coeffs: [f64; 8],
    coeffs_inv: [f64; 8],
}

impl PerspectiveTransform {

    pub fn from_point_f64(src_pts: &[PointF64; 4], dst_pts: &[PointF64; 4]) -> Self {
        let mut src_f64 = [0.; 8];
        let mut dst_f64 = [0.; 8];

        let mut i = 0;
        for pt in src_pts.iter() {
            src_f64[i] = pt.x; i += 1;
            src_f64[i] = pt.y; i += 1;
        }

        let mut i = 0;
        for pt in dst_pts.iter() {
            dst_f64[i] = pt.x; i += 1;
            dst_f64[i] = pt.y; i += 1;
        }

        Self::new(src_f64, dst_f64)
    }

    pub fn new(src_pts: [f64; 8], dst_pts: [f64; 8]) -> PerspectiveTransform {
        let coeffs = Self::get_normalization_coefficients(&src_pts, &dst_pts, false);
        let coeffs_inv = Self::get_normalization_coefficients(&src_pts, &dst_pts, true);
        PerspectiveTransform {
            coeffs,
            coeffs_inv,
        }
    }

    pub fn default() -> PerspectiveTransform {
        PerspectiveTransform {
            coeffs: Default::default(),
            coeffs_inv: Default::default(),
        }
    }

    fn get_normalization_coefficients(src_pts_in: &[f64; 8], dst_pts_in: &[f64; 8], is_inverse: bool) -> [f64; 8] {
        let (src_pts, dst_pts);
        if is_inverse {
            src_pts = dst_pts_in;
            dst_pts = src_pts_in;
        } else {
            src_pts = src_pts_in;
            dst_pts = dst_pts_in;
        }
        let r1 = [src_pts[0], src_pts[1], 1.0, 0.0, 0.0, 0.0, -1.0*dst_pts[0]*src_pts[0], -1.0*dst_pts[0]*src_pts[1]];
        let r2 = [0.0, 0.0, 0.0, src_pts[0], src_pts[1], 1.0, -1.0*dst_pts[1]*src_pts[0], -1.0*dst_pts[1]*src_pts[1]];
        let r3 = [src_pts[2], src_pts[3], 1.0, 0.0, 0.0, 0.0, -1.0*dst_pts[2]*src_pts[2], -1.0*dst_pts[2]*src_pts[3]];
        let r4 = [0.0, 0.0, 0.0, src_pts[2], src_pts[3], 1.0, -1.0*dst_pts[3]*src_pts[2], -1.0*dst_pts[3]*src_pts[3]];
        let r5 = [src_pts[4], src_pts[5], 1.0, 0.0, 0.0, 0.0, -1.0*dst_pts[4]*src_pts[4], -1.0*dst_pts[4]*src_pts[5]];
        let r6 = [0.0, 0.0, 0.0, src_pts[4], src_pts[5], 1.0, -1.0*dst_pts[5]*src_pts[4], -1.0*dst_pts[5]*src_pts[5]];
        let r7 = [src_pts[6], src_pts[7], 1.0, 0.0, 0.0, 0.0, -1.0*dst_pts[6]*src_pts[6], -1.0*dst_pts[6]*src_pts[7]];
        let r8 = [0.0, 0.0, 0.0, src_pts[6], src_pts[7], 1.0, -1.0*dst_pts[7]*src_pts[6], -1.0*dst_pts[7]*src_pts[7]];

        let mat_a = Matrix::new([r1, r2, r3, r4, r5, r6, r7, r8]);
        let mat_b = dst_pts.clone();
        let mat_c;

        if let Some(mat) = Matrix::inv(&Matrix::dot_mm_small(&Matrix::transpose(&mat_a), &mat_a)) {
            mat_c = mat;
        } else {
            return [1.,0.,0.,0., 1.,0.,0.,0.];
        }

        let mat_d = Matrix::dot_mm_small(&mat_c, &Matrix::transpose(&mat_a));
        let mut mat_x = Matrix::dot_mv(&mat_d, &mat_b);
        for i in 0..mat_x.len() {
            mat_x[i] = round(mat_x[i]);
        }

        return mat_x;

        fn round(num: f64) -> f64 {
            (num*10000000000.0).round()/10000000000.0
        }
    }

    pub fn transform(&self, point: PointF64) -> PointF64 {
        let (x, y) = (point.x, point.y);
        PointF64 {
            x: (self.coeffs[0]*x + self.coeffs[1]*y + self.coeffs[2]) / (self.coeffs[6]*x + self.coeffs[7]*y + 1.0),
            y: (self.coeffs[3]*x + self.coeffs[4]*y + self.coeffs[5]) / (self.coeffs[6]*x + self.coeffs[7]*y + 1.0)
        }
    }

    pub fn transform_inverse(&self, point: PointF64) -> PointF64 {
        let (x, y) = (point.x, point.y);
        PointF64 {
            x: (self.coeffs_inv[0]*x + self.coeffs_inv[1]*y + self.coeffs_inv[2]) / (self.coeffs_inv[6]*x + self.coeffs_inv[7]*y + 1.0),
            y: (self.coeffs_inv[3]*x + self.coeffs_inv[4]*y + self.coeffs_inv[5]) / (self.coeffs_inv[6]*x + self.coeffs_inv[7]*y + 1.0)
        }
    }

    pub fn print_coeffs(&self) -> String {
        format!("{:?}", self.coeffs)
    }
}