1#![forbid(unsafe_code)]
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ResolutionClass {
19 Sd,
20 Hd,
21 FullHd,
22 QuadHd,
23 UltraHd4k,
24 UltraHd8k,
25 Custom,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ResolutionError {
30 InvalidWidth,
31 InvalidHeight,
32 InvalidMaxWidth,
33 InvalidMaxHeight,
34 InvalidScale,
35 ScaledOutOfRange,
36}
37
38fn validate_dimensions(width: u32, height: u32) -> Result<(u32, u32), ResolutionError> {
39 if width == 0 {
40 return Err(ResolutionError::InvalidWidth);
41 }
42
43 if height == 0 {
44 return Err(ResolutionError::InvalidHeight);
45 }
46
47 Ok((width, height))
48}
49
50fn validate_max_dimensions(max_width: u32, max_height: u32) -> Result<(u32, u32), ResolutionError> {
51 if max_width == 0 {
52 return Err(ResolutionError::InvalidMaxWidth);
53 }
54
55 if max_height == 0 {
56 return Err(ResolutionError::InvalidMaxHeight);
57 }
58
59 Ok((max_width, max_height))
60}
61
62fn validate_scale(scale: f64) -> Result<f64, ResolutionError> {
63 if !scale.is_finite() || scale <= 0.0 {
64 Err(ResolutionError::InvalidScale)
65 } else {
66 Ok(scale)
67 }
68}
69
70pub fn classify_resolution(width: u32, height: u32) -> Result<ResolutionClass, ResolutionError> {
71 let (width, height) = validate_dimensions(width, height)?;
72
73 Ok(if width >= 7_680 && height >= 4_320 {
74 ResolutionClass::UltraHd8k
75 } else if width >= 3_840 && height >= 2_160 {
76 ResolutionClass::UltraHd4k
77 } else if width >= 2_560 && height >= 1_440 {
78 ResolutionClass::QuadHd
79 } else if width >= 1_920 && height >= 1_080 {
80 ResolutionClass::FullHd
81 } else if width >= 1_280 && height >= 720 {
82 ResolutionClass::Hd
83 } else if width >= 640 && height >= 480 {
84 ResolutionClass::Sd
85 } else {
86 ResolutionClass::Custom
87 })
88}
89
90pub fn megapixels(width: u32, height: u32) -> Result<f64, ResolutionError> {
91 Ok(pixels(width, height)? as f64 / 1_000_000.0)
92}
93
94pub fn pixels(width: u32, height: u32) -> Result<u64, ResolutionError> {
95 let (width, height) = validate_dimensions(width, height)?;
96 Ok(u64::from(width) * u64::from(height))
97}
98
99pub fn scale_dimensions(
100 width: u32,
101 height: u32,
102 scale: f64,
103) -> Result<(u32, u32), ResolutionError> {
104 let (width, height) = validate_dimensions(width, height)?;
105 let scale = validate_scale(scale)?;
106 let scaled_width = (f64::from(width) * scale).round();
107 let scaled_height = (f64::from(height) * scale).round();
108
109 if !(1.0..=f64::from(u32::MAX)).contains(&scaled_width)
110 || !(1.0..=f64::from(u32::MAX)).contains(&scaled_height)
111 {
112 return Err(ResolutionError::ScaledOutOfRange);
113 }
114
115 Ok((scaled_width as u32, scaled_height as u32))
116}
117
118pub fn fit_within(
119 width: u32,
120 height: u32,
121 max_width: u32,
122 max_height: u32,
123) -> Result<(u32, u32), ResolutionError> {
124 let (width, height) = validate_dimensions(width, height)?;
125 let (max_width, max_height) = validate_max_dimensions(max_width, max_height)?;
126
127 if width <= max_width && height <= max_height {
128 return Ok((width, height));
129 }
130
131 let scale =
132 (f64::from(max_width) / f64::from(width)).min(f64::from(max_height) / f64::from(height));
133 scale_dimensions(width, height, scale)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::{
139 ResolutionClass, ResolutionError, classify_resolution, fit_within, megapixels, pixels,
140 scale_dimensions,
141 };
142
143 #[test]
144 fn classifies_common_resolution_bands() {
145 assert_eq!(classify_resolution(640, 480).unwrap(), ResolutionClass::Sd);
146 assert_eq!(classify_resolution(1280, 720).unwrap(), ResolutionClass::Hd);
147 assert_eq!(
148 classify_resolution(1920, 1080).unwrap(),
149 ResolutionClass::FullHd
150 );
151 assert_eq!(
152 classify_resolution(3840, 2160).unwrap(),
153 ResolutionClass::UltraHd4k
154 );
155 assert_eq!(
156 classify_resolution(300, 200).unwrap(),
157 ResolutionClass::Custom
158 );
159 }
160
161 #[test]
162 fn computes_pixel_and_scaling_helpers() {
163 assert_eq!(pixels(1920, 1080).unwrap(), 2_073_600);
164 assert!((megapixels(1920, 1080).unwrap() - 2.0736).abs() < 1.0e-12);
165 assert_eq!(scale_dimensions(1920, 1080, 0.5).unwrap(), (960, 540));
166 assert_eq!(fit_within(3840, 2160, 1920, 1080).unwrap(), (1920, 1080));
167 assert_eq!(fit_within(1920, 1080, 1000, 1000).unwrap(), (1000, 563));
168 }
169
170 #[test]
171 fn rejects_invalid_resolution_inputs() {
172 assert_eq!(
173 classify_resolution(0, 1080),
174 Err(ResolutionError::InvalidWidth)
175 );
176 assert_eq!(
177 scale_dimensions(1920, 1080, 0.0),
178 Err(ResolutionError::InvalidScale)
179 );
180 assert_eq!(
181 fit_within(1920, 1080, 0, 1080),
182 Err(ResolutionError::InvalidMaxWidth)
183 );
184 }
185}