1use crate::Error;
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 validate(&self, what: &str) -> Result<(), Error> {
33 self.validate_nonzero(what)?;
34 if !self.width.is_multiple_of(2) || !self.height.is_multiple_of(2) {
35 return Err(Error::Codec(anyhow::anyhow!("{what} {self}: dimensions must be even")));
36 }
37 Ok(())
38 }
39
40 pub(crate) fn validate_encodable(&self, what: &str, framerate: u32) -> Result<(), Error> {
52 let pixels = self.pixels();
53 let representable = pixels.checked_mul(framerate as u64).is_some()
54 && usize::try_from(pixels).is_ok_and(|pixels| pixels.checked_mul(4).is_some());
55
56 if !representable {
57 return Err(Error::Codec(anyhow::anyhow!(
58 "{what} {self} at {framerate}fps: dimensions too large to represent"
59 )));
60 }
61 Ok(())
62 }
63
64 pub(crate) fn validate_nonzero(&self, what: &str) -> Result<(), Error> {
68 if self.width == 0 || self.height == 0 {
69 return Err(Error::Codec(anyhow::anyhow!(
70 "{what} {self}: dimensions must be non-zero"
71 )));
72 }
73 Ok(())
74 }
75}
76
77impl std::fmt::Display for Size {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(f, "{}x{}", self.width, self.height)
80 }
81}
82
83impl From<(u32, u32)> for Size {
84 fn from((width, height): (u32, u32)) -> Self {
85 Self::new(width, height)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn validate_rejects_odd_and_zero() {
95 assert!(Size::new(320, 240).validate("frame").is_ok());
96 assert!(Size::new(0, 240).validate("frame").is_err());
97 assert!(Size::new(320, 0).validate("frame").is_err());
98 assert!(Size::new(321, 240).validate("frame").is_err());
99 assert!(Size::new(320, 241).validate("frame").is_err());
100 }
101
102 #[test]
106 fn validate_encodable_rejects_unrepresentable_sizes() {
107 assert!(Size::new(65534, 65534).validate_encodable("frame", 30).is_ok());
110
111 assert!(
113 Size::new(u32::MAX - 1, u32::MAX - 1)
114 .validate_encodable("frame", 30)
115 .is_err()
116 );
117 assert!(Size::new(u32::MAX - 1, 2).validate_encodable("frame", 30).is_ok());
119 }
120
121 #[test]
122 fn display_reads_as_a_resolution() {
123 assert_eq!(Size::new(1920, 1080).to_string(), "1920x1080");
124 }
125}