use crate::{Error, Rate};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Size {
pub width: u32,
pub height: u32,
}
impl Size {
pub fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
pub fn pixels(&self) -> u64 {
self.width as u64 * self.height as u64
}
pub(crate) fn byte_len(&self, bytes_per_pixel: usize, what: &str) -> Result<usize, Error> {
usize::try_from(self.pixels())
.ok()
.and_then(|pixels| pixels.checked_mul(bytes_per_pixel))
.ok_or_else(|| Error::Codec(anyhow::anyhow!("{what} {self}: byte length is too large to represent")))
}
pub(crate) fn validate(&self, what: &str) -> Result<(), Error> {
self.validate_nonzero(what)?;
if !self.width.is_multiple_of(2) || !self.height.is_multiple_of(2) {
return Err(Error::Codec(anyhow::anyhow!("{what} {self}: dimensions must be even")));
}
Ok(())
}
pub(crate) fn validate_encodable(&self, what: &str, framerate: Rate) -> Result<(), Error> {
let pixels = self.pixels();
let representable = u128::from(pixels)
.checked_mul(u128::from(framerate.numerator()))
.is_some_and(|value| value / u128::from(framerate.denominator()) <= u128::from(u64::MAX))
&& usize::try_from(pixels).is_ok_and(|pixels| pixels.checked_mul(4).is_some());
if !representable {
return Err(Error::Codec(anyhow::anyhow!(
"{what} {self} at {framerate}fps: dimensions too large to represent"
)));
}
Ok(())
}
pub(crate) fn validate_nonzero(&self, what: &str) -> Result<(), Error> {
if self.width == 0 || self.height == 0 {
return Err(Error::Codec(anyhow::anyhow!(
"{what} {self}: dimensions must be non-zero"
)));
}
Ok(())
}
}
impl std::fmt::Display for Size {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}x{}", self.width, self.height)
}
}
impl From<(u32, u32)> for Size {
fn from((width, height): (u32, u32)) -> Self {
Self::new(width, height)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_rejects_odd_and_zero() {
assert!(Size::new(320, 240).validate("frame").is_ok());
assert!(Size::new(0, 240).validate("frame").is_err());
assert!(Size::new(320, 0).validate("frame").is_err());
assert!(Size::new(321, 240).validate("frame").is_err());
assert!(Size::new(320, 241).validate("frame").is_err());
}
#[test]
fn validate_encodable_rejects_unrepresentable_sizes() {
let rate = Rate::new(30, 1).unwrap();
assert!(Size::new(65534, 65534).validate_encodable("frame", rate).is_ok());
assert!(
Size::new(u32::MAX - 1, u32::MAX - 1)
.validate_encodable("frame", rate)
.is_err()
);
assert!(Size::new(u32::MAX - 1, 2).validate_encodable("frame", rate).is_ok());
let extreme = Rate::new(crate::rate::MAX_FRAMES_PER_SECOND, 1).unwrap();
assert!(
Size::new(u32::MAX - 1, u32::MAX - 1)
.validate_encodable("frame", extreme)
.is_err()
);
}
#[test]
fn display_reads_as_a_resolution() {
assert_eq!(Size::new(1920, 1080).to_string(), "1920x1080");
}
}