use crate::boxes::BoxBuilder;
use crate::model::{ColourInformation, IsoBmffImage, Item, PropertyKind};
#[must_use]
pub fn write(image: &IsoBmffImage) -> Vec<u8> {
let mut bb = BoxBuilder::new();
write_ftyp(&mut bb, image);
let extent_slots = write_meta(&mut bb, image);
let mdat_start = bb.begin_box(b"mdat");
let mut payload_positions = Vec::with_capacity(image.items.len());
for item in &image.items {
payload_positions.push(bb.len());
bb.bytes(&item.payload);
}
bb.end_box(mdat_start);
for (slot, pos) in extent_slots.into_iter().zip(payload_positions) {
bb.patch_u32(slot, pos as u32);
}
bb.into_vec()
}
fn write_ftyp(bb: &mut BoxBuilder, image: &IsoBmffImage) {
let start = bb.begin_box(b"ftyp");
bb.bytes(&image.major_brand);
bb.u32(image.minor_version);
for brand in &image.compatible_brands {
bb.bytes(brand);
}
bb.end_box(start);
}
fn write_meta(bb: &mut BoxBuilder, image: &IsoBmffImage) -> Vec<usize> {
let start = bb.begin_box(b"meta");
bb.full_box(0, 0);
write_hdlr(bb);
write_pitm(bb, image.primary_item_id);
let extent_slots = write_iloc(bb, &image.items);
write_iinf(bb, &image.items);
write_iprp(bb, &image.items);
bb.end_box(start);
extent_slots
}
fn write_hdlr(bb: &mut BoxBuilder) {
let start = bb.begin_box(b"hdlr");
bb.full_box(0, 0);
bb.u32(0); bb.bytes(b"pict"); bb.u32(0); bb.u32(0); bb.u32(0); bb.u8(0); bb.end_box(start);
}
fn write_pitm(bb: &mut BoxBuilder, primary_item_id: u16) {
let start = bb.begin_box(b"pitm");
bb.full_box(0, 0);
bb.u16(primary_item_id);
bb.end_box(start);
}
fn write_iloc(bb: &mut BoxBuilder, items: &[Item]) -> Vec<usize> {
let start = bb.begin_box(b"iloc");
bb.full_box(0, 0);
bb.u8(0x44); bb.u8(0x00); bb.u16(items.len() as u16); let mut slots = Vec::with_capacity(items.len());
for item in items {
bb.u16(item.id); bb.u16(0); bb.u16(1); slots.push(bb.reserve_u32()); bb.u32(item.payload.len() as u32); }
bb.end_box(start);
slots
}
fn write_iinf(bb: &mut BoxBuilder, items: &[Item]) {
let start = bb.begin_box(b"iinf");
bb.full_box(0, 0);
bb.u16(items.len() as u16); for item in items {
let infe = bb.begin_box(b"infe");
bb.full_box(2, 0); bb.u16(item.id); bb.u16(0); bb.bytes(&item.item_type); bb.bytes(item.name.as_bytes()); bb.u8(0); bb.end_box(infe);
}
bb.end_box(start);
}
fn write_iprp(bb: &mut BoxBuilder, items: &[Item]) {
let mut pool: Vec<Vec<u8>> = Vec::new();
let mut assoc: Vec<Vec<(usize, bool)>> = Vec::with_capacity(items.len());
for item in items {
let mut row = Vec::with_capacity(item.properties.len());
for property in &item.properties {
let bytes = serialize_property(&property.kind);
let index = match pool.iter().position(|p| *p == bytes) {
Some(i) => i + 1,
None => {
pool.push(bytes);
pool.len()
}
};
row.push((index, property.essential));
}
assoc.push(row);
}
let start = bb.begin_box(b"iprp");
let ipco = bb.begin_box(b"ipco");
for property in &pool {
bb.bytes(property);
}
bb.end_box(ipco);
write_ipma(bb, items, &assoc);
bb.end_box(start);
}
fn write_ipma(bb: &mut BoxBuilder, items: &[Item], assoc: &[Vec<(usize, bool)>]) {
let start = bb.begin_box(b"ipma");
bb.full_box(0, 0);
bb.u32(items.len() as u32); for (item, row) in items.iter().zip(assoc) {
bb.u16(item.id);
debug_assert!(row.len() <= usize::from(u8::MAX));
bb.u8(row.len() as u8); for &(index, essential) in row {
debug_assert!(index <= 0x7f, "ipma v0 holds at most 127 properties");
let byte = index as u8;
bb.u8(if essential { byte + 0x80 } else { byte });
}
}
bb.end_box(start);
}
fn serialize_property(kind: &PropertyKind) -> Vec<u8> {
let mut bb = BoxBuilder::new();
match kind {
PropertyKind::ImageSpatialExtents { width, height } => {
let start = bb.begin_box(b"ispe");
bb.full_box(0, 0);
bb.u32(*width);
bb.u32(*height);
bb.end_box(start);
}
PropertyKind::PixelInformation { bits_per_channel } => {
let start = bb.begin_box(b"pixi");
bb.full_box(0, 0);
debug_assert!(bits_per_channel.len() <= usize::from(u8::MAX));
bb.u8(bits_per_channel.len() as u8);
for &bits in bits_per_channel {
bb.u8(bits);
}
bb.end_box(start);
}
PropertyKind::Colour(ColourInformation::Nclx(c)) => {
let start = bb.begin_box(b"colr");
bb.bytes(b"nclx");
bb.u16(c.colour_primaries);
bb.u16(c.transfer_characteristics);
bb.u16(c.matrix_coefficients);
bb.u8(u8::from(c.full_range) << 7); bb.end_box(start);
}
PropertyKind::Rotation(angle) => {
let start = bb.begin_box(b"irot");
bb.u8(angle & 0x03); bb.end_box(start);
}
PropertyKind::Mirror(axis) => {
let start = bb.begin_box(b"imir");
bb.u8(axis & 0x01); bb.end_box(start);
}
PropertyKind::CodecConfiguration { kind, data } | PropertyKind::Other { kind, data } => {
let start = bb.begin_box(kind);
bb.bytes(data);
bb.end_box(start);
}
}
bb.into_vec()
}