This commit is contained in:
bflatastic 2026-10-13 11:02:38 +07:00
commit e9892a6f9d
2 changed files with 29 additions and 7 deletions

View File

@ -51,11 +51,11 @@ impl Homography {
let c01 = m[5] * m[6] - m[3] * m[8];
let c02 = m[3] * m[7] - m[4] * m[6];
let det = m[0] * c00 + m[1] * c01 + m[2] * c02;
if det.abs() < 1e-300 {
if !det.is_finite() || det.abs() < 1e-300 {
return None;
}
let inv = 1.0 / det;
Some(Homography([
let coefficients = [
c00 * inv,
(m[2] * m[7] - m[1] * m[8]) * inv,
(m[1] * m[5] - m[2] * m[4]) * inv,
@ -65,11 +65,17 @@ impl Homography {
c02 * inv,
(m[1] * m[6] - m[0] * m[7]) * inv,
(m[0] * m[4] - m[1] * m[3]) * inv,
]))
];
coefficients.iter().all(|v| v.is_finite()).then_some(Homography(coefficients))
}
/// The homography mapping `src[i]` → `dst[i]` (4 correspondences, DLT with h33 = 1).
pub fn from_quads(src: &[Point; 4], dst: &[Point; 4]) -> Option<Homography> {
// Failed feature matches and invalid perspective parameters can contain
// non-finite coordinates. Do not return a transform that poisons rendering.
if src.iter().chain(dst.iter()).any(|p| !p.x.is_finite() || !p.y.is_finite()) {
return None;
}
let mut a = [[0.0f64; 9]; 8];
for i in 0..4 {
let (x, y) = (src[i].x, src[i].y);
@ -80,7 +86,7 @@ impl Homography {
// Gaussian elimination with partial pivoting on the 8×8 system (last column = rhs).
for col in 0..8 {
let piv = (col..8).max_by(|&i, &j| a[i][col].abs().total_cmp(&a[j][col].abs()))?;
if a[piv][col].abs() < 1e-12 {
if !a[piv][col].is_finite() || a[piv][col].abs() < 1e-12 {
return None;
}
a.swap(col, piv);
@ -94,7 +100,7 @@ impl Homography {
}
}
let h: Vec<f64> = (0..8).map(|i| a[i][8] / a[i][i]).collect();
Some(Homography([h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], 1.0]))
h.iter().all(|v| v.is_finite()).then_some(Homography([h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], 1.0]))
}
}
@ -121,4 +127,19 @@ mod tests {
let src = [Point::new(0.0, 0.0); 4];
assert!(Homography::from_quads(&src, &src).is_none());
}
#[test]
fn nonfinite_projective_and_affine_transforms_are_rejected() {
let src = [Point::new(0.0, 0.0), Point::new(1.0, 0.0), Point::new(1.0, 1.0), Point::new(0.0, 1.0)];
let mut invalid = src;
invalid[2].x = f64::NAN;
assert!(Homography::from_quads(&invalid, &src).is_none());
invalid[2].x = f64::INFINITY;
assert!(Homography::from_quads(&src, &invalid).is_none());
assert!(Homography([f64::NAN, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]).inverse().is_none());
assert!(Homography([f64::INFINITY, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]).inverse().is_none());
assert!(Affine([f64::NAN, 0.0, 0.0, 1.0, 0.0, 0.0]).inverse().is_none());
assert!(Affine([f64::INFINITY, 0.0, 0.0, 1.0, 0.0, 0.0]).inverse().is_none());
assert_eq!(Affine::IDENTITY.inverse(), Some(Affine::IDENTITY));
}
}

View File

@ -234,12 +234,13 @@ impl Affine {
}
pub fn inverse(&self) -> Option<Affine> {
let det = self.determinant();
if det.abs() < 1e-300 {
if !det.is_finite() || det.abs() < 1e-300 {
return None;
}
let [a, b, c, d, e, f] = self.0;
let inv = 1.0 / det;
Some(Affine([d * inv, -b * inv, -c * inv, a * inv, (c * f - d * e) * inv, (b * e - a * f) * inv]))
let coefficients = [d * inv, -b * inv, -c * inv, a * inv, (c * f - d * e) * inv, (b * e - a * f) * inv];
coefficients.iter().all(|v| v.is_finite()).then_some(Affine(coefficients))
}
/// Bounding box of a transformed rectangle.
pub fn transform_rect_bbox(&self, r: &Rect) -> Rect {