use gamut_core::{Dimensions, Error, Result};
use crate::vp8l::bit_io::{BitReader, BitWriter};
pub const VP8L_SIGNATURE: u8 = 0x2f;
pub const VP8L_MAX_DIMENSION: u16 = 1 << 14;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Vp8lHeader {
pub width: u16,
pub height: u16,
pub alpha_is_used: bool,
pub version: u8,
}
impl Vp8lHeader {
pub fn from_dimensions(dims: Dimensions, alpha_is_used: bool) -> Result<Self> {
let max = u32::from(VP8L_MAX_DIMENSION);
if dims.width == 0 || dims.height == 0 || dims.width > max || dims.height > max {
return Err(Error::InvalidInput(
"VP8L: dimensions out of range (1..=16384)",
));
}
Ok(Self {
width: dims.width as u16,
height: dims.height as u16,
alpha_is_used,
version: 0,
})
}
#[must_use]
pub fn dimensions(&self) -> Dimensions {
Dimensions {
width: u32::from(self.width),
height: u32::from(self.height),
}
}
pub fn read(r: &mut BitReader<'_>) -> Result<Self> {
if r.read_bits(8)? != u32::from(VP8L_SIGNATURE) {
return Err(Error::InvalidInput("VP8L: bad signature byte"));
}
let width = (r.read_bits(14)? + 1) as u16;
let height = (r.read_bits(14)? + 1) as u16;
let alpha_is_used = r.read_bits(1)? != 0;
let version = r.read_bits(3)? as u8;
if version != 0 {
return Err(Error::InvalidInput("VP8L: unsupported version (must be 0)"));
}
Ok(Self {
width,
height,
alpha_is_used,
version,
})
}
pub fn write(&self, w: &mut BitWriter) {
w.write_bits(u32::from(VP8L_SIGNATURE), 8);
w.write_bits(u32::from(self.width.saturating_sub(1)), 14);
w.write_bits(u32::from(self.height.saturating_sub(1)), 14);
w.write_bits(u32::from(self.alpha_is_used), 1);
w.write_bits(u32::from(self.version), 3);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip(header: Vp8lHeader) -> Vp8lHeader {
let mut w = BitWriter::new();
header.write(&mut w);
let bytes = w.finish();
let mut r = BitReader::new(&bytes);
Vp8lHeader::read(&mut r).expect("valid header")
}
#[test]
fn round_trips_min_and_max_dimensions() {
for (w, h, alpha) in [(1u16, 1u16, false), (16384, 16384, true), (17, 9, false)] {
let header = Vp8lHeader {
width: w,
height: h,
alpha_is_used: alpha,
version: 0,
};
assert_eq!(round_trip(header), header);
}
}
#[test]
fn from_dimensions_validates_range() {
assert!(
Vp8lHeader::from_dimensions(
Dimensions {
width: 1,
height: 1
},
false
)
.is_ok()
);
let max = Dimensions {
width: 16384,
height: 16384,
};
assert_eq!(
Vp8lHeader::from_dimensions(max, true).unwrap().dimensions(),
max
);
for bad in [
Dimensions {
width: 0,
height: 5,
},
Dimensions {
width: 5,
height: 0,
},
Dimensions {
width: 16385,
height: 1,
},
Dimensions {
width: 1,
height: 16385,
},
] {
assert!(matches!(
Vp8lHeader::from_dimensions(bad, false),
Err(Error::InvalidInput(_))
));
}
}
#[test]
fn rejects_bad_signature() {
let bytes = [0x2e, 0, 0, 0, 0];
let mut r = BitReader::new(&bytes);
assert!(matches!(
Vp8lHeader::read(&mut r),
Err(Error::InvalidInput(_))
));
}
#[test]
fn rejects_nonzero_version() {
let mut w = BitWriter::new();
w.write_bits(u32::from(VP8L_SIGNATURE), 8);
w.write_bits(0, 14);
w.write_bits(0, 14);
w.write_bits(0, 1);
w.write_bits(1, 3); let bytes = w.finish();
let mut r = BitReader::new(&bytes);
assert!(matches!(
Vp8lHeader::read(&mut r),
Err(Error::InvalidInput(_))
));
}
#[test]
fn rejects_truncated_header() {
let mut r = BitReader::new(&[0x2f, 0x00]); assert!(matches!(
Vp8lHeader::read(&mut r),
Err(Error::InvalidInput(_))
));
}
}