Skip to main content

rskit_media/stream/
spatial.rs

1//! Spatial types for video and image processing.
2
3use serde::{Deserialize, Serialize};
4
5/// Width × Height in pixels.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct Resolution {
8    /// Width in pixels.
9    pub width: u32,
10    /// Height in pixels.
11    pub height: u32,
12}
13
14impl Resolution {
15    /// Create a new resolution.
16    pub fn new(w: u32, h: u32) -> Self {
17        Self {
18            width: w,
19            height: h,
20        }
21    }
22
23    /// 360p (640×360).
24    pub fn p360() -> Self {
25        Self::new(640, 360)
26    }
27    /// 480p (854×480).
28    pub fn p480() -> Self {
29        Self::new(854, 480)
30    }
31    /// 720p (1280×720).
32    pub fn p720() -> Self {
33        Self::new(1280, 720)
34    }
35    /// 1080p (1920×1080).
36    pub fn p1080() -> Self {
37        Self::new(1920, 1080)
38    }
39    /// 1440p (2560×1440).
40    pub fn p1440() -> Self {
41        Self::new(2560, 1440)
42    }
43    /// 4K (3840×2160).
44    pub fn p4k() -> Self {
45        Self::new(3840, 2160)
46    }
47
48    /// Aspect ratio as a simplified fraction (e.g., (16, 9)).
49    pub fn aspect_ratio(&self) -> (u32, u32) {
50        let g = gcd(self.width, self.height);
51        match (self.width.checked_div(g), self.height.checked_div(g)) {
52            (Some(w), Some(h)) => (w, h),
53            _ => (0, 0),
54        }
55    }
56
57    /// Aspect ratio as a floating-point value (width / height).
58    pub fn aspect_ratio_f64(&self) -> f64 {
59        if self.height == 0 {
60            0.0
61        } else {
62            self.width as f64 / self.height as f64
63        }
64    }
65
66    /// Whether the image is taller than it is wide.
67    pub fn is_portrait(&self) -> bool {
68        self.height > self.width
69    }
70
71    /// Whether the image is wider than it is tall.
72    pub fn is_landscape(&self) -> bool {
73        self.width > self.height
74    }
75
76    /// Whether width equals height.
77    pub fn is_square(&self) -> bool {
78        self.width == self.height
79    }
80
81    /// Total pixel count.
82    pub fn pixel_count(&self) -> u64 {
83        self.width as u64 * self.height as u64
84    }
85
86    /// Scale to fit within the given bounds, preserving aspect ratio.
87    pub fn scale_to_fit(&self, max_width: u32, max_height: u32) -> Self {
88        let w_ratio = max_width as f64 / self.width as f64;
89        let h_ratio = max_height as f64 / self.height as f64;
90        let ratio = w_ratio.min(h_ratio);
91        Self::new(
92            (self.width as f64 * ratio).round() as u32,
93            (self.height as f64 * ratio).round() as u32,
94        )
95    }
96
97    /// Scale to fill the given bounds, preserving aspect ratio (may overflow bounds).
98    pub fn scale_to_fill(&self, width: u32, height: u32) -> Self {
99        let w_ratio = width as f64 / self.width as f64;
100        let h_ratio = height as f64 / self.height as f64;
101        let ratio = w_ratio.max(h_ratio);
102        Self::new(
103            (self.width as f64 * ratio).round() as u32,
104            (self.height as f64 * ratio).round() as u32,
105        )
106    }
107
108    /// Scale by a factor (e.g., 0.5 for half size, 2.0 for double).
109    pub fn scale_by(&self, factor: f64) -> Self {
110        Self::new(
111            (self.width as f64 * factor).round() as u32,
112            (self.height as f64 * factor).round() as u32,
113        )
114    }
115}
116
117fn gcd(a: u32, b: u32) -> u32 {
118    if b == 0 { a } else { gcd(b, a % b) }
119}
120
121/// Rational frame rate (numerator / denominator) for exact representation.
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
123pub struct FrameRate {
124    /// Numerator.
125    pub num: u32,
126    /// Denominator.
127    pub den: u32,
128}
129
130impl FrameRate {
131    /// Create a new frame rate.
132    pub fn new(num: u32, den: u32) -> Self {
133        Self { num, den }
134    }
135
136    /// Create from an integer FPS value.
137    pub fn fps(n: u32) -> Self {
138        Self::new(n, 1)
139    }
140
141    /// 24 fps.
142    pub fn fps_24() -> Self {
143        Self::new(24, 1)
144    }
145    /// 25 fps.
146    pub fn fps_25() -> Self {
147        Self::new(25, 1)
148    }
149    /// 30 fps.
150    pub fn fps_30() -> Self {
151        Self::new(30, 1)
152    }
153    /// 50 fps.
154    pub fn fps_50() -> Self {
155        Self::new(50, 1)
156    }
157    /// 60 fps.
158    pub fn fps_60() -> Self {
159        Self::new(60, 1)
160    }
161    /// NTSC 29.97 fps.
162    pub fn ntsc_30() -> Self {
163        Self::new(30000, 1001)
164    }
165    /// NTSC 23.976 fps.
166    pub fn ntsc_24() -> Self {
167        Self::new(24000, 1001)
168    }
169    /// NTSC 59.94 fps.
170    pub fn ntsc_60() -> Self {
171        Self::new(60000, 1001)
172    }
173
174    /// Convert to floating-point FPS.
175    pub fn as_f64(&self) -> f64 {
176        if self.den == 0 {
177            0.0
178        } else {
179            self.num as f64 / self.den as f64
180        }
181    }
182}