mod boxes;
mod writer;
use crate::boxes::*;
use arrayvec::ArrayVec;
use std::io;
pub struct Aviffy {
premultiplied_alpha: bool,
}
pub fn serialize<W: io::Write>(into_output: W, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> io::Result<()> {
Aviffy::new().write(into_output, color_av1_data, alpha_av1_data, width, height, depth_bits)
}
impl Aviffy {
pub fn new() -> Self {
Self {
premultiplied_alpha: false,
}
}
pub fn premultiplied_alpha(&mut self, is_premultiplied: bool) -> &mut Self {
self.premultiplied_alpha = is_premultiplied;
self
}
pub fn write<W: io::Write>(&self, into_output: W, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> io::Result<()> {
let mut image_items = ArrayVec::new();
let mut iloc_items = ArrayVec::new();
let mut av1c_items = ArrayVec::new();
let mut compatible_brands = ArrayVec::new();
let mut ipma_entries = ArrayVec::new();
let mut data_chunks = ArrayVec::<[&[u8]; 4]>::new();
let mut irefs = ArrayVec::new();
let mut auxc = None;
let color_image_id = 1;
let alpha_image_id = 2;
let high_bitdepth = depth_bits >= 10;
let twelve_bit = depth_bits >= 12;
image_items.push(InfeBox {
id: color_image_id,
typ: FourCC(*b"av01"),
name: "",
});
av1c_items.push(Av1CBox {
seq_profile: false,
seq_level_idx_0: 0,
seq_tier_0: false,
high_bitdepth,
twelve_bit,
monochrome: false,
chroma_subsampling_x: false,
chroma_subsampling_y: false,
chroma_sample_position: 0,
});
ipma_entries.push(IpmaEntry {
item_id: color_image_id,
prop_ids: [1, 2].iter().copied().collect(),
});
if let Some(alpha_data) = alpha_av1_data {
image_items.push(InfeBox {
id: alpha_image_id,
typ: FourCC(*b"av01"),
name: "",
});
av1c_items.push(Av1CBox {
seq_profile: false,
seq_level_idx_0: 0,
seq_tier_0: false,
high_bitdepth,
twelve_bit,
monochrome: true,
chroma_subsampling_x: false,
chroma_subsampling_y: false,
chroma_sample_position: 0,
});
auxc = Some(AuxCBox {
urn: "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha",
});
irefs.push(IrefBox {
entry: IrefEntryBox {
from_id: alpha_image_id,
to_id: color_image_id,
typ: FourCC(*b"auxl"),
},
});
if self.premultiplied_alpha {
irefs.push(IrefBox {
entry: IrefEntryBox {
from_id: color_image_id,
to_id: alpha_image_id,
typ: FourCC(*b"prem"),
},
});
}
ipma_entries.push(IpmaEntry {
item_id: alpha_image_id,
prop_ids: [3, 4].iter().copied().collect(),
});
iloc_items.push(IlocItem {
id: color_image_id,
extents: [
IlocExtent {
offset: IlocOffset::Relative(alpha_data.len()),
len: color_av1_data.len(),
},
].into(),
});
iloc_items.push(IlocItem {
id: alpha_image_id,
extents: [
IlocExtent {
offset: IlocOffset::Relative(0),
len: alpha_data.len(),
},
].into(),
});
data_chunks.push(alpha_data);
data_chunks.push(color_av1_data);
} else {
compatible_brands.push(FourCC(*b"mif1"));
iloc_items.push(IlocItem {
id: color_image_id,
extents: [
IlocExtent {
offset: IlocOffset::Relative(0),
len: color_av1_data.len(),
},
].into(),
});
data_chunks.push(color_av1_data);
}
let mut boxes = AvifFile {
ftyp: FtypBox {
major_brand: FourCC(*b"avif"),
minor_version: 0,
compatible_brands,
},
meta: MetaBox {
iinf: IinfBox { items: image_items },
pitm: PitmBox(color_image_id),
iloc: IlocBox { items: iloc_items },
iprp: IprpBox {
ipco: IpcoBox {
ispe: IspeBox { width, height },
av1c: av1c_items,
auxc,
},
ipma: IpmaBox {
entries: ipma_entries,
},
},
iref: irefs,
},
mdat: MdatBox {
data_chunks: &data_chunks,
},
};
boxes.write(into_output)
}
pub fn to_vec(&self, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> Vec<u8> {
let mut out = Vec::with_capacity(color_av1_data.len() + alpha_av1_data.map_or(0, |a| a.len()) + 400);
self.write(&mut out, color_av1_data, alpha_av1_data, width, height, depth_bits).unwrap();
out
}
}
pub fn serialize_to_vec(color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> Vec<u8> {
Aviffy::new().to_vec(color_av1_data, alpha_av1_data, width, height, depth_bits)
}
#[test]
fn test_roundtrip_parse_mp4() {
let test_img = b"av12356abc";
let avif = serialize_to_vec(test_img, None, 10, 20, 8);
let mut ctx = mp4parse::AvifContext::new();
mp4parse::read_avif(&mut avif.as_slice(), &mut ctx).unwrap();
assert_eq!(&test_img[..], ctx.primary_item.as_slice());
}
#[test]
fn test_roundtrip_parse_avif() {
let test_img = [1,2,3,4,5,6];
let test_alpha = [77,88,99];
let avif = serialize_to_vec(&test_img, Some(&test_alpha), 10, 20, 8);
let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
assert_eq!(&test_img[..], ctx.primary_item.as_slice());
assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap());
}
#[test]
fn premultiplied_flag() {
let test_img = [1,2,3,4];
let test_alpha = [55,66,77,88,99];
let avif = Aviffy::new().premultiplied_alpha(true).to_vec(&test_img, Some(&test_alpha), 5, 5, 8);
let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
assert!(ctx.premultiplied_alpha);
assert_eq!(&test_img[..], ctx.primary_item.as_slice());
assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap());
}