use super::{read_u24_le, write_u24_le};
use crate::error::{Error, Result};
use crate::image::{Dimensions, Metadata};
pub const VP8X_PAYLOAD_LEN: usize = 10;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct Vp8xFlags(pub u8);
impl Vp8xFlags {
const ICC: u8 = 0x20;
const ALPHA: u8 = 0x10;
const EXIF: u8 = 0x08;
const XMP: u8 = 0x04;
const ANIMATION: u8 = 0x02;
#[must_use]
pub const fn has_icc(self) -> bool {
self.0 & Self::ICC != 0
}
#[must_use]
pub const fn has_alpha(self) -> bool {
self.0 & Self::ALPHA != 0
}
#[must_use]
pub const fn has_exif(self) -> bool {
self.0 & Self::EXIF != 0
}
#[must_use]
pub const fn has_xmp(self) -> bool {
self.0 & Self::XMP != 0
}
#[must_use]
pub const fn is_animated(self) -> bool {
self.0 & Self::ANIMATION != 0
}
#[must_use]
pub const fn for_output(metadata: &Metadata, has_alpha: bool) -> Self {
let mut bits = 0u8;
if metadata.icc_profile.is_some() {
bits |= Self::ICC;
}
if has_alpha {
bits |= Self::ALPHA;
}
if metadata.exif.is_some() {
bits |= Self::EXIF;
}
if metadata.xmp.is_some() {
bits |= Self::XMP;
}
Self(bits)
}
#[must_use]
pub const fn with_animation(self) -> Self {
Self(self.0 | Self::ANIMATION)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Vp8xInfo {
pub flags: Vp8xFlags,
pub canvas: Dimensions,
}
impl Vp8xInfo {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() != VP8X_PAYLOAD_LEN {
return Err(Error::InvalidContainer);
}
let flags = Vp8xFlags(data[0]);
let width = read_u24_le(data[4], data[5], data[6]) + 1;
let height = read_u24_le(data[7], data[8], data[9]) + 1;
let canvas = Dimensions::new(width, height).map_err(|_| Error::InvalidContainer)?;
Ok(Self { flags, canvas })
}
#[must_use]
pub const fn build(flags: Vp8xFlags, canvas: Dimensions) -> [u8; VP8X_PAYLOAD_LEN] {
let w = write_u24_le(canvas.width() - 1);
let h = write_u24_le(canvas.height() - 1);
[flags.0, 0, 0, 0, w[0], w[1], w[2], h[0], h[1], h[2]]
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::{Vp8xFlags, Vp8xInfo};
use crate::error::Error;
use crate::image::{Dimensions, Metadata};
proptest! {
#[test]
fn build_parse_round_trips(flags in any::<u8>(), w in 1u32..=16384, h in 1u32..=16384) {
let info = Vp8xInfo {
flags: Vp8xFlags(flags),
canvas: Dimensions::new(w, h).unwrap(),
};
let parsed = Vp8xInfo::parse(&Vp8xInfo::build(info.flags, info.canvas)).unwrap();
prop_assert_eq!(parsed, info);
}
}
#[test]
fn flags_for_output_and_accessors() {
let metadata = Metadata {
icc_profile: Some(vec![1]),
exif: Some(vec![2]),
..Metadata::none()
};
let f = Vp8xFlags::for_output(&metadata, false);
assert!(f.has_icc() && f.has_exif());
assert!(!f.has_alpha() && !f.has_xmp() && !f.is_animated());
assert_eq!(f.0, 0x20 | 0x08);
let raw = Vp8xFlags(0x10 | 0x02);
assert!(raw.has_alpha() && raw.is_animated());
}
#[test]
fn accessors_read_only_their_own_bit() {
let none = Vp8xFlags(0);
assert!(!none.has_icc());
assert!(!none.has_alpha());
assert!(!none.has_exif());
assert!(!none.has_xmp());
assert!(!none.is_animated());
assert!(Vp8xFlags(0x20).has_icc());
assert!(Vp8xFlags(0x10).has_alpha());
assert!(Vp8xFlags(0x08).has_exif());
assert!(Vp8xFlags(0x04).has_xmp());
assert!(Vp8xFlags(0x02).is_animated());
}
#[test]
fn with_animation_sets_the_bit_and_preserves_others() {
assert_eq!(Vp8xFlags(0x20).with_animation().0, 0x22);
assert_eq!(Vp8xFlags(0x02).with_animation().0, 0x02);
}
#[test]
fn parse_build_round_trips() {
let canvas = Dimensions::new(640, 480).unwrap();
let metadata = Metadata {
icc_profile: Some(vec![1]),
xmp: Some(vec![2]),
..Metadata::none()
};
let flags = Vp8xFlags::for_output(&metadata, true); let bytes = Vp8xInfo::build(flags, canvas);
let info = Vp8xInfo::parse(&bytes).unwrap();
assert_eq!(info.flags, flags);
assert_eq!(info.canvas, canvas);
}
#[test]
fn build_stores_canvas_minus_one() {
let bytes = Vp8xInfo::build(Vp8xFlags::default(), Dimensions::new(1, 1).unwrap());
assert_eq!(&bytes[4..10], &[0, 0, 0, 0, 0, 0]);
let bytes = Vp8xInfo::build(Vp8xFlags::default(), Dimensions::new(256, 2).unwrap());
assert_eq!(&bytes[4..7], &[255, 0, 0]); assert_eq!(&bytes[7..10], &[1, 0, 0]); }
#[test]
fn parse_rejects_bad_length_and_oversized_canvas() {
assert_eq!(
Vp8xInfo::parse(&[0u8; 9]).unwrap_err(),
Error::InvalidContainer
);
let bad = [0u8, 0, 0, 0, 0xff, 0xff, 0xff, 0, 0, 0];
assert_eq!(Vp8xInfo::parse(&bad).unwrap_err(), Error::InvalidContainer);
}
#[test]
fn parse_ignores_reserved_bytes() {
let mut bytes = Vp8xInfo::build(Vp8xFlags(0x10), Dimensions::new(8, 8).unwrap());
bytes[1] = 0xff; bytes[2] = 0xff;
bytes[3] = 0xff;
let info = Vp8xInfo::parse(&bytes).unwrap();
assert!(info.flags.has_alpha());
assert_eq!(info.canvas, Dimensions::new(8, 8).unwrap());
}
}