use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BackgroundColor {
pub blue: u8,
pub green: u8,
pub red: u8,
pub alpha: u8,
}
impl BackgroundColor {
pub const fn as_u32_le(&self) -> u32 {
(self.blue as u32)
| ((self.green as u32) << 8)
| ((self.red as u32) << 16)
| ((self.alpha as u32) << 24)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AnimError {
BadPayloadLength {
got: usize,
},
}
impl fmt::Display for AnimError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BadPayloadLength { got } => write!(
f,
"ANIM payload must be 6 bytes per §2.7.1.1 Figure 8, got {got}"
),
}
}
}
impl std::error::Error for AnimError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AnimHeader {
pub background_color: BackgroundColor,
pub loop_count: u16,
}
impl AnimHeader {
pub fn parse(payload: &[u8]) -> Result<Self, AnimError> {
if payload.len() != 6 {
return Err(AnimError::BadPayloadLength { got: payload.len() });
}
let background_color = BackgroundColor {
blue: payload[0],
green: payload[1],
red: payload[2],
alpha: payload[3],
};
let loop_count = u16::from_le_bytes([payload[4], payload[5]]);
Ok(Self {
background_color,
loop_count,
})
}
pub const fn loops_forever(&self) -> bool {
self.loop_count == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
fn anim(b: u8, g: u8, r: u8, a: u8, loop_count: u16) -> Vec<u8> {
let mut v = Vec::with_capacity(6);
v.push(b);
v.push(g);
v.push(r);
v.push(a);
v.extend_from_slice(&loop_count.to_le_bytes());
v
}
#[test]
fn payload_must_be_exactly_six_bytes() {
assert_eq!(
AnimHeader::parse(&[]),
Err(AnimError::BadPayloadLength { got: 0 })
);
assert_eq!(
AnimHeader::parse(&[0u8; 5]),
Err(AnimError::BadPayloadLength { got: 5 })
);
assert_eq!(
AnimHeader::parse(&[0u8; 7]),
Err(AnimError::BadPayloadLength { got: 7 })
);
}
#[test]
fn all_zero_payload_decodes_to_transparent_black_infinite_loop() {
let h = AnimHeader::parse(&[0u8; 6]).unwrap();
assert_eq!(h.background_color.blue, 0);
assert_eq!(h.background_color.green, 0);
assert_eq!(h.background_color.red, 0);
assert_eq!(h.background_color.alpha, 0);
assert_eq!(h.background_color.as_u32_le(), 0);
assert_eq!(h.loop_count, 0);
assert!(h.loops_forever());
}
#[test]
fn background_color_byte_order_is_bgra() {
let h = AnimHeader::parse(&anim(0x10, 0x20, 0x30, 0x40, 5)).unwrap();
assert_eq!(h.background_color.blue, 0x10);
assert_eq!(h.background_color.green, 0x20);
assert_eq!(h.background_color.red, 0x30);
assert_eq!(h.background_color.alpha, 0x40);
assert_eq!(h.background_color.as_u32_le(), 0x4030_2010);
}
#[test]
fn loop_count_is_little_endian_u16() {
let h = AnimHeader::parse(&[0, 0, 0, 0, 0x02, 0x01]).unwrap();
assert_eq!(h.loop_count, 0x0102);
assert!(!h.loops_forever());
}
#[test]
fn maximum_loop_count_decodes_and_is_not_infinite() {
let h = AnimHeader::parse(&[0, 0, 0, 0, 0xFF, 0xFF]).unwrap();
assert_eq!(h.loop_count, 0xFFFF);
assert!(!h.loops_forever());
}
#[test]
fn fixture_animated_with_alpha_decodes_to_white_opaque_infinite() {
let h = AnimHeader::parse(&[0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00]).unwrap();
assert_eq!(h.background_color.blue, 0xFF);
assert_eq!(h.background_color.green, 0xFF);
assert_eq!(h.background_color.red, 0xFF);
assert_eq!(h.background_color.alpha, 0xFF);
assert_eq!(h.background_color.as_u32_le(), 0xFFFF_FFFF);
assert_eq!(h.loop_count, 0);
assert!(h.loops_forever());
}
}