1use crate::{Error, Rate};
2
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
9pub struct Size {
10 pub width: u32,
12 pub height: u32,
14}
15
16impl Size {
17 pub fn new(width: u32, height: u32) -> Self {
19 Self { width, height }
20 }
21
22 pub fn pixels(&self) -> u64 {
24 self.width as u64 * self.height as u64
25 }
26
27 pub(crate) fn byte_len(&self, bytes_per_pixel: usize, what: &str) -> Result<usize, Error> {
28 usize::try_from(self.pixels())
29 .ok()
30 .and_then(|pixels| pixels.checked_mul(bytes_per_pixel))
31 .ok_or_else(|| Error::Codec(anyhow::anyhow!("{what} {self}: byte length is too large to represent")))
32 }
33
34 pub(crate) fn validate(&self, what: &str) -> Result<(), Error> {
40 self.validate_nonzero(what)?;
41 if !self.width.is_multiple_of(2) || !self.height.is_multiple_of(2) {
42 return Err(Error::Codec(anyhow::anyhow!("{what} {self}: dimensions must be even")));
43 }
44 Ok(())
45 }
46
47 pub(crate) fn validate_encodable(&self, what: &str, framerate: Rate) -> Result<(), Error> {
59 let pixels = self.pixels();
60 let representable = u128::from(pixels)
61 .checked_mul(u128::from(framerate.numerator()))
62 .is_some_and(|value| value / u128::from(framerate.denominator()) <= u128::from(u64::MAX))
63 && usize::try_from(pixels).is_ok_and(|pixels| pixels.checked_mul(4).is_some());
64
65 if !representable {
66 return Err(Error::Codec(anyhow::anyhow!(
67 "{what} {self} at {framerate}fps: dimensions too large to represent"
68 )));
69 }
70 Ok(())
71 }
72
73 pub(crate) fn validate_nonzero(&self, what: &str) -> Result<(), Error> {
77 if self.width == 0 || self.height == 0 {
78 return Err(Error::Codec(anyhow::anyhow!(
79 "{what} {self}: dimensions must be non-zero"
80 )));
81 }
82 Ok(())
83 }
84}
85
86impl std::fmt::Display for Size {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 write!(f, "{}x{}", self.width, self.height)
89 }
90}
91
92impl From<(u32, u32)> for Size {
93 fn from((width, height): (u32, u32)) -> Self {
94 Self::new(width, height)
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn validate_rejects_odd_and_zero() {
104 assert!(Size::new(320, 240).validate("frame").is_ok());
105 assert!(Size::new(0, 240).validate("frame").is_err());
106 assert!(Size::new(320, 0).validate("frame").is_err());
107 assert!(Size::new(321, 240).validate("frame").is_err());
108 assert!(Size::new(320, 241).validate("frame").is_err());
109 }
110
111 #[test]
115 fn validate_encodable_rejects_unrepresentable_sizes() {
116 let rate = Rate::new(30, 1).unwrap();
119 assert!(Size::new(65534, 65534).validate_encodable("frame", rate).is_ok());
120
121 assert!(
123 Size::new(u32::MAX - 1, u32::MAX - 1)
124 .validate_encodable("frame", rate)
125 .is_err()
126 );
127 assert!(Size::new(u32::MAX - 1, 2).validate_encodable("frame", rate).is_ok());
129 let extreme = Rate::new(crate::rate::MAX_FRAMES_PER_SECOND, 1).unwrap();
130 assert!(
131 Size::new(u32::MAX - 1, u32::MAX - 1)
132 .validate_encodable("frame", extreme)
133 .is_err()
134 );
135 }
136
137 #[test]
138 fn display_reads_as_a_resolution() {
139 assert_eq!(Size::new(1920, 1080).to_string(), "1920x1080");
140 }
141}