#![allow(clippy::excessive_precision)]
use approx::assert_abs_diff_eq;
use lensfun::Lens;
use lensfun::lens::LensType;
use lensfun::mod_pc::{
Direction, SvdNoConvergence, apply_correction_kernel, apply_distortion_kernel,
build_perspective_state, svd,
};
use lensfun::modifier::Modifier;
const FOCAL: f32 = 50.89;
const CROP: f32 = 1.534;
const TOL_5K: f32 = f32::EPSILON * 5e3;
const TOL_2K: f32 = f32::EPSILON * 2e3;
fn rectilinear_lens() -> Lens {
Lens {
lens_type: LensType::Rectilinear,
crop_factor: 1.0,
aspect_ratio: 1.5,
..Lens::default()
}
}
fn modifier_landscape() -> Modifier {
Modifier::new(&rectilinear_lens(), FOCAL, CROP, 1500, 1000, false)
}
fn modifier_portrait() -> Modifier {
Modifier::new(&rectilinear_lens(), FOCAL, CROP, 1000, 1500, false)
}
#[test]
fn svd_matches_upstream() {
let x = [1.0_f64, 2.0, 3.0, 2.0, 1.0];
let y = [1.0_f64, 2.0, 2.0, 0.0, 1.5];
let mut m = Vec::with_capacity(5);
for i in 0..5 {
m.push(vec![x[i] * x[i], x[i] * y[i], y[i] * y[i], x[i], y[i], 1.0]);
}
let result = svd(m).expect("converges");
let eps = f64::EPSILON * 5.0;
let expected = [
0.04756514941544937,
0.09513029883089875,
0.1902605976617977,
-0.4280863447390447,
-0.5707817929853928,
0.6659120918162917,
];
for i in 0..6 {
assert!(
(result[i] - expected[i]).abs() <= eps,
"i={i}: got {}, want {}",
result[i],
expected[i],
);
}
}
#[test]
fn pc_4_points_landscape() {
let mut m = modifier_landscape();
let x = [503.0_f32, 1063.0, 509.0, 1066.0];
let y = [150.0_f32, 197.0, 860.0, 759.0];
assert!(m.enable_perspective_correction(&x, &y, 0.0));
let expected_x = [
194.93747_f32,
283.76633,
367.27982,
445.94156,
520.16211,
590.3075,
656.70416,
719.64496,
779.39276,
836.18463,
];
let expected_y = [
-88.539207_f32,
45.526031,
171.56934,
290.28986,
402.30765,
508.1748,
608.38434,
703.37805,
793.55273,
879.26605,
];
for i in 0..10 {
let mut coords = [0.0_f32; 2];
assert!(m.apply_geometry_distortion(100.0 * i as f32, 100.0 * i as f32, 1, 1, &mut coords));
assert_abs_diff_eq!(coords[0], expected_x[i], epsilon = TOL_5K);
assert_abs_diff_eq!(coords[1], expected_y[i], epsilon = TOL_5K);
}
}
#[test]
fn pc_4_points_portrait() {
let mut m = modifier_portrait();
let x = [145.0_f32, 208.0, 748.0, 850.0];
let y = [1060.0_f32, 666.0, 668.0, 1060.0];
assert!(m.enable_perspective_correction(&x, &y, 0.0));
let expected_x = [
71.087723_f32,
147.83899,
228.25151,
312.59366,
401.16068,
494.27817,
592.30609,
695.64337,
804.73334,
920.07019,
];
let expected_y = [
508.74167_f32,
536.03046,
564.62103,
594.60876,
626.09875,
659.20654,
694.06024,
730.80176,
769.58856,
810.5965,
];
for i in 0..10 {
let mut coords = [0.0_f32; 2];
assert!(m.apply_geometry_distortion(100.0 * i as f32, 100.0 * i as f32, 1, 1, &mut coords));
assert_abs_diff_eq!(coords[0], expected_x[i], epsilon = TOL_2K);
assert_abs_diff_eq!(coords[1], expected_y[i], epsilon = TOL_2K);
}
}
#[test]
fn pc_8_points() {
let mut m = modifier_landscape();
let x = [615.0_f32, 264.0, 1280.0, 813.0, 615.0, 1280.0, 264.0, 813.0];
let y = [755.0_f32, 292.0, 622.0, 220.0, 755.0, 622.0, 292.0, 220.0];
assert!(m.enable_perspective_correction(&x, &y, 0.0));
let expected_x = [
-111.952522_f32,
7.50310612,
129.942596,
255.479279,
384.231903,
516.325867,
651.893066,
791.071838,
934.008728,
1080.85803,
];
let expected_y = [
395.10733_f32,
422.476837,
450.529816,
479.292572,
508.792175,
539.057251,
570.118042,
602.006531,
634.755859,
668.401611,
];
for i in 0..10 {
let mut coords = [0.0_f32; 2];
assert!(m.apply_geometry_distortion(100.0 * i as f32, 100.0 * i as f32, 1, 1, &mut coords));
assert_abs_diff_eq!(coords[0], expected_x[i], epsilon = TOL_5K);
assert_abs_diff_eq!(coords[1], expected_y[i], epsilon = TOL_5K);
}
}
#[test]
fn pc_zero_points_rejected() {
let mut m = modifier_landscape();
assert!(!m.enable_perspective_correction(&[], &[], 0.0));
for i in 0..10 {
let mut coords = [0.0_f32; 2];
assert!(!m.apply_geometry_distortion(
100.0 * i as f32,
100.0 * i as f32,
1,
1,
&mut coords
));
}
}
#[test]
fn pc_5_points_ellipse() {
let mut m = modifier_landscape();
let x = [661.0_f32, 594.0, 461.0, 426.0, 530.0];
let y = [501.0_f32, 440.0, 442.0, 534.0, 562.0];
assert!(m.enable_perspective_correction(&x, &y, 0.0));
let expected_x = [
-115.54961_f32,
22.151754,
151.93915,
274.47577,
390.35281,
500.09869,
604.18756,
703.04572,
797.05792,
886.5719,
];
let expected_y = [
209.91034_f32,
274.73886,
335.84152,
393.53049,
448.0842,
499.75153,
548.75549,
595.297,
639.55695,
681.69928,
];
for i in 0..10 {
let mut coords = [0.0_f32; 2];
assert!(m.apply_geometry_distortion(100.0 * i as f32, 100.0 * i as f32, 1, 1, &mut coords));
assert_abs_diff_eq!(coords[0], expected_x[i], epsilon = TOL_5K);
assert_abs_diff_eq!(coords[1], expected_y[i], epsilon = TOL_5K);
}
}
#[test]
fn pc_7_points() {
let mut m = modifier_landscape();
let x = [661.0_f32, 594.0, 461.0, 426.0, 530.0, 302.0, 815.0];
let y = [501.0_f32, 440.0, 442.0, 534.0, 562.0, 491.0, 279.0];
assert!(m.enable_perspective_correction(&x, &y, 0.0));
let expected_x = [
-138.18913_f32,
3.8870707,
144.48228,
283.6199,
421.32199,
557.61121,
692.50861,
826.03589,
958.21338,
1089.0619,
];
let expected_y = [
522.41956_f32,
532.48621,
542.44806,
552.30658,
562.06348,
571.72015,
581.27826,
590.73932,
600.10474,
609.37598,
];
for i in 0..10 {
let mut coords = [0.0_f32; 2];
assert!(m.apply_geometry_distortion(100.0 * i as f32, 100.0 * i as f32, 1, 1, &mut coords));
assert_abs_diff_eq!(coords[0], expected_x[i], epsilon = TOL_5K);
assert_abs_diff_eq!(coords[1], expected_y[i], epsilon = TOL_5K);
}
}
#[test]
fn correction_then_distortion_is_near_identity() {
let xn = vec![-0.30_f64, 0.30, -0.28, 0.32];
let yn = vec![-0.20_f64, -0.18, 0.20, 0.18];
let fwd = build_perspective_state(&xn, &yn, 0.0, false).expect("forward");
let rev = build_perspective_state(&xn, &yn, 0.0, true).expect("reverse");
assert_eq!(fwd.direction, Direction::Correction);
assert_eq!(rev.direction, Direction::Distortion);
let samples = [[-0.1_f32, 0.05], [0.0, 0.0], [0.2, -0.1], [-0.05, 0.15]];
for s in &samples {
let mut buf = [s[0], s[1]];
apply_correction_kernel(&fwd, &mut buf);
apply_distortion_kernel(&rev, &mut buf);
assert_abs_diff_eq!(buf[0], s[0], epsilon = 1e-3);
assert_abs_diff_eq!(buf[1], s[1], epsilon = 1e-3);
}
}
#[test]
fn svd_no_convergence_displays() {
let e = SvdNoConvergence;
let s = format!("{e}");
assert!(s.contains("SVD"));
}