ndi_sdk_sys/
resolution.rs

1use std::fmt::{Debug, Display};
2
3#[derive(Clone, Copy, PartialEq, Eq, Hash)]
4pub struct Resolution {
5    pub x: usize,
6    pub y: usize,
7}
8
9impl Resolution {
10    /// # Panics
11    ///
12    /// Panics if the given resolution is not safe ([Resolution::is_safe])
13    pub fn new(x: usize, y: usize) -> Self {
14        let res = Resolution { x, y };
15        res.check_is_safe();
16        res
17    }
18
19    pub fn try_new(x: usize, y: usize) -> Option<Self> {
20        if Resolution::is_safe(x, y) {
21            Some(Resolution { x, y })
22        } else {
23            None
24        }
25    }
26
27    pub const fn new_const(x: usize, y: usize) -> Self {
28        assert!(
29            Resolution::is_safe(x, y),
30            "Resolution is not safe (more info is not available in the const version of the constructor)"
31        );
32        Resolution { x, y }
33    }
34
35    pub fn from_i32(x: i32, y: i32) -> Self {
36        let res = Resolution {
37            x: x.try_into()
38                .expect("Invalid x-resolution, failed to cast to usize (x = {x})"),
39            y: y.try_into()
40                .expect("Invalid y-resolution, failed to cast to usize (y = {y})"),
41        };
42        res.check_is_safe();
43        res
44    }
45
46    pub const fn to_i32(&self) -> (i32, i32) {
47        (self.x as i32, self.y as i32)
48    }
49
50    pub fn aspect_ratio(&self) -> f64 {
51        self.x as f64 / self.y as f64
52    }
53
54    pub const fn pixels(&self) -> usize {
55        // Invariant: this type cannot be constructed with unsafe values from the outside
56        self.x * self.y
57    }
58
59    fn check_is_safe(&self) {
60        assert!(
61            Resolution::is_safe(self.x, self.x),
62            "Resolution is not safe: {}x{}",
63            self.x,
64            self.y
65        );
66    }
67
68    /// Checks if the resolution is safe to handle
69    ///
70    /// A resolution is considered unsafe if
71    /// - one component is zero
72    /// - `width * height * 4 color components * 16bit` exceeds i32::MAX
73    /// - width is not divisible by 2
74    pub const fn is_safe(x: usize, y: usize) -> bool {
75        const MAX_SAFE_VALUE: usize = i32::MAX as usize;
76        const MAX_PIXEL_BYTES: usize = 8; // 4 components x 16bit
77        const MAX_SAFE_PIXELS: usize = MAX_SAFE_VALUE / MAX_PIXEL_BYTES;
78
79        if x == 0 || y == 0 {
80            return false;
81        }
82
83        // https://docs.ndi.video/all/developing-with-ndi/sdk/frame-types#video-frames-ndilib_video_frame_v2_t
84        // >  Note that, because data is internally all considered in 4:2:2 formats, image width values should be divisible by two.
85        if x & 0b1 != 0 {
86            return false;
87        }
88
89        if x >= MAX_SAFE_VALUE || y >= MAX_SAFE_VALUE {
90            return false;
91        }
92
93        if let Some(area) = usize::checked_mul(x, y) {
94            area < MAX_SAFE_PIXELS
95        } else {
96            false
97        }
98    }
99}
100
101impl Display for Resolution {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        write!(f, "{}x{}", self.x, self.y)
104    }
105}
106
107impl Debug for Resolution {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        write!(f, "Resolution({}x{})", self.x, self.y)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_resolution_safe() {
119        _ = Resolution::new(1920, 1080);
120    }
121
122    #[test]
123    fn test_resolution_unsafe() {
124        fn assert_unsafe(x: usize, y: usize) {
125            assert!(
126                !Resolution::is_safe(x, y),
127                "Resolution {}x{} should be unsafe",
128                x,
129                y
130            );
131        }
132
133        // zero dimensions
134        assert_unsafe(0, 1080);
135        assert_unsafe(1920, 0);
136
137        // mul overflow
138        assert_unsafe(i32::MAX as usize, 2);
139    }
140}