#![allow(dead_code)]
use crate::avif_export::AvifExport;
use super::encoder::encode_av1_intra;
use super::isobmff::{
begin_box, end_box, write_cstring, write_full_box_header, write_u16_be, write_u32_be,
};
pub fn write_avif(export: &AvifExport) -> Vec<u8> {
let w = export.options.width;
let h = export.options.height;
let base_q_idx = export.options.preset.quantizer();
let av1_data = encode_av1_intra(&export.pixels, w, h, base_q_idx);
let mut out = Vec::new();
{
let start = begin_box(&mut out, b"ftyp");
out.extend_from_slice(b"avif"); write_u32_be(&mut out, 0); out.extend_from_slice(b"avif");
out.extend_from_slice(b"mif1");
out.extend_from_slice(b"miaf");
end_box(&mut out, start);
}
let mdat_start = begin_box(&mut out, b"mdat");
let av1_data_offset = (mdat_start + 8) as u32;
out.extend_from_slice(&av1_data);
end_box(&mut out, mdat_start);
let av1_data_len = av1_data.len() as u32;
let meta_start = begin_box(&mut out, b"meta");
write_full_box_header(&mut out, 0, 0);
{
let s = begin_box(&mut out, b"hdlr");
write_full_box_header(&mut out, 0, 0);
write_u32_be(&mut out, 0); out.extend_from_slice(b"pict"); out.extend_from_slice(&[0u8; 12]); write_cstring(&mut out, "AVIF");
end_box(&mut out, s);
}
{
let s = begin_box(&mut out, b"pitm");
write_full_box_header(&mut out, 0, 0);
write_u16_be(&mut out, 1); end_box(&mut out, s);
}
{
let s = begin_box(&mut out, b"iloc");
write_full_box_header(&mut out, 0, 0);
out.push(0x44); out.push(0x00); write_u16_be(&mut out, 1); write_u16_be(&mut out, 1); write_u16_be(&mut out, 0); write_u16_be(&mut out, 1); write_u32_be(&mut out, av1_data_offset); write_u32_be(&mut out, av1_data_len); end_box(&mut out, s);
}
{
let s = begin_box(&mut out, b"iinf");
write_full_box_header(&mut out, 0, 0);
write_u16_be(&mut out, 1);
let se = begin_box(&mut out, b"infe");
write_full_box_header(&mut out, 2, 0);
write_u16_be(&mut out, 1); write_u16_be(&mut out, 0); out.extend_from_slice(b"av01"); write_cstring(&mut out, "Color");
end_box(&mut out, se);
end_box(&mut out, s);
}
{
let s = begin_box(&mut out, b"iprp");
let sc = begin_box(&mut out, b"ipco");
{
let sp = begin_box(&mut out, b"ispe");
write_full_box_header(&mut out, 0, 0);
write_u32_be(&mut out, w);
write_u32_be(&mut out, h);
end_box(&mut out, sp);
}
{
let sp = begin_box(&mut out, b"av1C");
out.push(0x81); out.push(0x20); out.push(0x00); out.push(0x00); end_box(&mut out, sp);
}
end_box(&mut out, sc);
{
let sa = begin_box(&mut out, b"ipma");
write_full_box_header(&mut out, 0, 0);
write_u32_be(&mut out, 1);
write_u16_be(&mut out, 1); out.push(2);
out.push(0x01);
out.push(0x82); end_box(&mut out, sa);
}
end_box(&mut out, s); }
end_box(&mut out, meta_start);
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::av1::decoder::decode_av1_intra;
use crate::avif_export::{AvifExport, AvifOptions, AvifPreset};
fn make_export(w: u32, h: u32, preset: AvifPreset) -> AvifExport {
let pixels: Vec<[u8; 4]> = (0..(w * h) as usize)
.map(|i| {
let v = (i * 4) as u8;
[v, v.wrapping_add(50), v.wrapping_add(100), 255]
})
.collect();
AvifExport {
options: AvifOptions {
width: w,
height: h,
preset,
alpha: true,
},
pixels,
}
}
fn lossless_export(w: u32, h: u32) -> AvifExport {
let pixels: Vec<[u8; 4]> = (0..(w * h) as usize)
.map(|i| {
let v = (i * 4) as u8;
[v, v.wrapping_add(50), v.wrapping_add(100), 255]
})
.collect();
AvifExport {
options: AvifOptions {
width: w,
height: h,
preset: AvifPreset::Best, alpha: true,
},
pixels,
}
}
#[test]
fn test_write_avif_produces_ftyp_brand() {
let export = make_export(4, 4, AvifPreset::Best);
let bytes = write_avif(&export);
assert!(bytes.len() > 12, "AVIF file must have at least ftyp + mdat");
let ftyp_size = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
assert_eq!(&bytes[4..8], b"ftyp", "first box must be ftyp");
assert!(ftyp_size >= 20, "ftyp box must be at least 20 bytes");
assert_eq!(&bytes[8..12], b"avif", "major brand must be 'avif'");
}
#[test]
fn test_write_avif_contains_mdat() {
let export = make_export(4, 4, AvifPreset::Best);
let bytes = write_avif(&export);
let ftyp_size =
u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
let mdat_offset = ftyp_size;
assert!(
bytes.len() > mdat_offset + 8,
"file must contain mdat box after ftyp"
);
assert_eq!(
&bytes[mdat_offset + 4..mdat_offset + 8],
b"mdat",
"second box must be mdat"
);
}
#[test]
fn test_write_avif_nonempty() {
let export = make_export(8, 8, AvifPreset::Balanced);
let bytes = write_avif(&export);
assert!(bytes.len() > 100, "AVIF file must contain substantial data");
}
#[test]
fn test_avif_lossless_roundtrip_via_obu_extraction() {
let w = 8u32;
let h = 8u32;
let pixels: Vec<[u8; 4]> = (0..(w * h) as usize)
.map(|i| {
let v = (i * 4) as u8;
[v, v.wrapping_add(50), v.wrapping_add(100), 255]
})
.collect();
let av1_bytes = encode_av1_intra(&pixels, w, h, 0);
let (decoded, dw, dh) =
decode_av1_intra(&av1_bytes).expect("decode must succeed");
assert_eq!(dw, w);
assert_eq!(dh, h);
assert_eq!(decoded.len(), pixels.len());
for (i, (&orig, &dec)) in pixels.iter().zip(decoded.iter()).enumerate() {
assert_eq!(orig[0], dec[0], "R mismatch at pixel {i}");
assert_eq!(orig[1], dec[1], "G mismatch at pixel {i}");
assert_eq!(orig[2], dec[2], "B mismatch at pixel {i}");
}
}
#[test]
fn test_write_avif_to_tempfile() {
use std::io::Write;
let export = make_export(4, 4, AvifPreset::Best);
let bytes = write_avif(&export);
let mut path = std::env::temp_dir();
path.push("oxihuman_test_avif_writer.avif");
let mut f = std::fs::File::create(&path).expect("cannot create temp file");
f.write_all(&bytes).expect("cannot write AVIF bytes");
let metadata = std::fs::metadata(&path).expect("cannot stat file");
assert!(metadata.len() > 0, "temp file must be non-empty");
let _ = std::fs::remove_file(&path);
}
}