Skip to main content

ff_filter/effects/
lens_profile.rs

1//! Predefined lens distortion correction profiles for common cameras.
2
3/// Predefined lens distortion correction profiles for common cameras.
4///
5/// Each variant stores the radial coefficients (`k1`, `k2`) and a `scale`
6/// factor that zooms slightly to hide the warped border pixels left after
7/// correction.
8///
9/// Use with [`FilterGraph::lens_profile`](crate::FilterGraph::lens_profile).
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum LensProfile {
12    /// `GoPro` Hero 9 / 10 / 11 wide-angle mode (heavy barrel distortion).
13    GoproHero9Wide,
14    /// `GoPro` Hero 11 linear mode (mild distortion).
15    GoproHero11Linear,
16    /// Apple iPhone 14 Pro main camera (mild barrel).
17    Iphone14ProMain,
18    /// DJI Mini 3 Pro wide-angle lens.
19    DjiMini3ProWide,
20    /// User-supplied coefficients for any other camera.
21    Custom {
22        /// First-order radial coefficient (−1.0 to 1.0).
23        k1: f32,
24        /// Second-order radial coefficient (−1.0 to 1.0).
25        k2: f32,
26        /// Uniform scale applied after correction to hide warped border pixels.
27        /// 1.0 = no scale.
28        scale: f32,
29    },
30}
31
32impl LensProfile {
33    /// Return `(k1, k2, scale)` for this profile.
34    pub fn coefficients(&self) -> (f32, f32, f32) {
35        match self {
36            Self::GoproHero9Wide => (-0.21, 0.05, 1.05),
37            Self::GoproHero11Linear => (-0.04, 0.01, 1.01),
38            Self::Iphone14ProMain => (-0.03, 0.00, 1.01),
39            Self::DjiMini3ProWide => (-0.16, 0.03, 1.03),
40            Self::Custom { k1, k2, scale } => (*k1, *k2, *scale),
41        }
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn gopro_hero9_wide_should_have_expected_coefficients() {
51        let (k1, k2, scale) = LensProfile::GoproHero9Wide.coefficients();
52        assert!((k1 - (-0.21_f32)).abs() < f32::EPSILON);
53        assert!((k2 - 0.05_f32).abs() < f32::EPSILON);
54        assert!((scale - 1.05_f32).abs() < f32::EPSILON);
55    }
56
57    #[test]
58    fn iphone14_pro_main_should_have_expected_coefficients() {
59        let (k1, k2, scale) = LensProfile::Iphone14ProMain.coefficients();
60        assert!((k1 - (-0.03_f32)).abs() < f32::EPSILON);
61        assert!((k2 - 0.00_f32).abs() < f32::EPSILON);
62        assert!((scale - 1.01_f32).abs() < f32::EPSILON);
63    }
64
65    #[test]
66    fn custom_should_return_supplied_values() {
67        let (k1, k2, scale) = LensProfile::Custom {
68            k1: -0.1,
69            k2: 0.02,
70            scale: 1.02,
71        }
72        .coefficients();
73        assert!((k1 - (-0.1_f32)).abs() < f32::EPSILON);
74        assert!((k2 - 0.02_f32).abs() < f32::EPSILON);
75        assert!((scale - 1.02_f32).abs() < f32::EPSILON);
76    }
77
78    #[test]
79    fn custom_identity_should_return_zero_k1_k2_and_unit_scale() {
80        let (k1, k2, scale) = LensProfile::Custom {
81            k1: 0.0,
82            k2: 0.0,
83            scale: 1.0,
84        }
85        .coefficients();
86        assert!((k1 - 0.0_f32).abs() < f32::EPSILON);
87        assert!((k2 - 0.0_f32).abs() < f32::EPSILON);
88        assert!((scale - 1.0_f32).abs() < f32::EPSILON);
89    }
90}