use crate::color::{Cicp, ColorMetadata};
use crate::error::EncodeError;
use crate::fmt::{BitDepth, ChromaFormat};
use crate::metadata::{ImageMetadata, Orientation};
const NUT_SPS: u8 = 15;
const NUT_PPS: u8 = 16;
struct Nal<'a> {
nal_type: u8,
data: &'a [u8],
}
fn split_annexb(stream: &[u8]) -> Vec<Nal<'_>> {
let mut starts: Vec<usize> = Vec::new();
let mut i = 0usize;
while i + 3 <= stream.len() {
if stream[i] == 0 && stream[i + 1] == 0 && stream[i + 2] == 1 {
starts.push(i + 3);
i += 3;
} else {
i += 1;
}
}
let mut nals = Vec::with_capacity(starts.len());
for (k, &s) in starts.iter().enumerate() {
let mut end = if k + 1 < starts.len() {
starts[k + 1] - 3
} else {
stream.len()
};
if end > s && k + 1 < starts.len() && stream[end - 1] == 0 {
end -= 1;
}
if end >= s + 2 {
let data = &stream[s..end];
let nal_type = (data[1] >> 3) & 0x1f;
nals.push(Nal { nal_type, data });
}
}
nals
}
fn w32(buf: &mut Vec<u8>, v: u32) {
buf.extend_from_slice(&v.to_be_bytes());
}
fn w16(buf: &mut Vec<u8>, v: u16) {
buf.extend_from_slice(&v.to_be_bytes());
}
fn write_box(buf: &mut Vec<u8>, cc: &[u8; 4]) -> usize {
let s = buf.len();
w32(buf, 0); buf.extend_from_slice(cc);
s
}
fn write_fullbox(buf: &mut Vec<u8>, cc: &[u8; 4], ver: u8, flags: u32) -> usize {
let s = write_box(buf, cc);
buf.push(ver);
buf.extend_from_slice(&flags.to_be_bytes()[1..]); s
}
fn patch(buf: &mut [u8], start: usize) {
let size = (buf.len() - start) as u32;
buf[start..start + 4].copy_from_slice(&size.to_be_bytes());
}
fn stored_extent(width: u32, height: u32, chroma: ChromaFormat) -> (u32, u32) {
let sub_w = chroma.sub_w() as u32;
let sub_h = chroma.sub_h() as u32;
(
width.div_ceil(sub_w) * sub_w,
height.div_ceil(sub_h) * sub_h,
)
}
fn coded_extent(width: u32, height: u32) -> (u32, u32) {
((width + 7) & !7, (height + 7) & !7)
}
fn write_clap(buf: &mut Vec<u8>, width: u32, height: u32, stored_w: u32, stored_h: u32) {
let s = write_box(buf, b"clap");
w32(buf, width); w32(buf, 1); w32(buf, height); w32(buf, 1); w32(buf, width.wrapping_sub(stored_w)); w32(buf, 2); w32(buf, height.wrapping_sub(stored_h)); w32(buf, 2); patch(buf, s);
}
fn profile_idc(chroma: ChromaFormat) -> u8 {
match chroma {
ChromaFormat::Yuv422 | ChromaFormat::Yuv444 => 33,
_ => 1,
}
}
fn build_vvcc(
sps: &[&[u8]],
pps: &[&[u8]],
chroma_idc: u8,
bit_depth: u8,
width: u16,
height: u16,
profile: u8,
) -> Vec<u8> {
let mut r: Vec<u8> = Vec::new();
r.push(0xf8 | (3 << 1) | 1);
w16(&mut r, (1 << 4) | (1 << 2) | (chroma_idc as u16 & 0x3));
r.push(((bit_depth - 8) << 5) | 0x1f);
r.push(0x01);
r.push(profile << 1);
r.push(crate::headers::LEVEL_IDC);
r.push(0x80);
r.push(0x00);
w16(&mut r, width);
w16(&mut r, height);
w16(&mut r, 0);
let arrays: &[(u8, &[&[u8]])] = &[(NUT_SPS, sps), (NUT_PPS, pps)];
let present = arrays.iter().filter(|(_, l)| !l.is_empty()).count();
r.push(present as u8);
for &(nut, list) in arrays {
if list.is_empty() {
continue;
}
r.push(0x80 | (nut & 0x1f));
w16(&mut r, list.len() as u16);
for &nalu in list {
w16(&mut r, nalu.len() as u16);
r.extend_from_slice(nalu);
}
}
r
}
fn write_ftyp(f: &mut Vec<u8>) {
let s = write_box(f, b"ftyp");
f.extend_from_slice(b"mif1"); w32(f, 0); f.extend_from_slice(b"mif1");
f.extend_from_slice(b"miaf");
patch(f, s);
}
fn write_colr_nclx(f: &mut Vec<u8>, cicp: &crate::color::Cicp) {
let s = write_box(f, b"colr");
f.extend_from_slice(&cicp.nclx_payload());
patch(f, s);
}
fn write_colr_icc(f: &mut Vec<u8>, icc: &[u8]) {
let s = write_box(f, b"colr");
f.extend_from_slice(b"prof");
f.extend_from_slice(icc);
patch(f, s);
}
pub(crate) fn wrap_vvc_still(
annexb: &[u8],
width: u32,
height: u32,
bit_depth: BitDepth,
chroma: ChromaFormat,
color: &ColorMetadata,
meta: &ImageMetadata,
) -> Result<Vec<u8>, EncodeError> {
let chroma = chroma.for_dimensions(width, height);
let (stored_w, stored_h) = stored_extent(width, height, chroma);
let (coded_w, coded_h) = coded_extent(width, height);
let needs_clap = (stored_w, stored_h) != (width, height);
let nals = split_annexb(annexb);
if nals.is_empty() {
return Err(EncodeError::Unsupported(
"empty VVC stream: nothing to wrap",
));
}
let sps: Vec<&[u8]> = nals
.iter()
.filter(|n| n.nal_type == NUT_SPS)
.map(|n| n.data)
.collect();
let pps: Vec<&[u8]> = nals
.iter()
.filter(|n| n.nal_type == NUT_PPS)
.map(|n| n.data)
.collect();
let vcl: Vec<&[u8]> = nals
.iter()
.filter(|n| n.nal_type <= 11)
.map(|n| n.data)
.collect();
if sps.is_empty() || pps.is_empty() || vcl.is_empty() {
return Err(EncodeError::Unsupported(
"VVC stream missing SPS, PPS or slice data; cannot wrap",
));
}
let bd = bit_depth.bits();
let chroma_idc = chroma.idc() as u8;
let vvcc = build_vvcc(
&sps,
&pps,
chroma_idc,
bd,
coded_w.min(0xffff) as u16,
coded_h.min(0xffff) as u16,
profile_idc(chroma),
);
let mut sample: Vec<u8> = Vec::new();
for &nalu in &vcl {
w32(&mut sample, nalu.len() as u32);
sample.extend_from_slice(nalu);
}
let has_exif = meta.exif.is_some();
let exif_payload: Vec<u8> = meta
.exif
.as_ref()
.map(|e| {
let mut p = Vec::with_capacity(e.len() + 4);
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(e);
p
})
.unwrap_or_default();
let chans: u8 = if matches!(chroma, ChromaFormat::Monochrome) {
1
} else {
3
};
let mut f: Vec<u8> = Vec::new();
write_ftyp(&mut f);
let meta_start = write_fullbox(&mut f, b"meta", 0, 0);
{
let s = write_fullbox(&mut f, b"hdlr", 0, 0);
w32(&mut f, 0); f.extend_from_slice(b"pict");
w32(&mut f, 0);
w32(&mut f, 0);
w32(&mut f, 0);
f.push(0); patch(&mut f, s);
}
{
let s = write_fullbox(&mut f, b"pitm", 0, 0);
w16(&mut f, 1);
patch(&mut f, s);
}
let img_offset_patch_pos;
let mut exif_offset_patch_pos = 0usize;
{
let s = write_fullbox(&mut f, b"iloc", 0, 0);
f.push(0x44); f.push(0x00); w16(&mut f, if has_exif { 2 } else { 1 }); w16(&mut f, 1); w16(&mut f, 0); w16(&mut f, 1); img_offset_patch_pos = f.len();
w32(&mut f, 0); w32(&mut f, sample.len() as u32); if has_exif {
w16(&mut f, 2); w16(&mut f, 0); w16(&mut f, 1); exif_offset_patch_pos = f.len();
w32(&mut f, 0); w32(&mut f, exif_payload.len() as u32); }
patch(&mut f, s);
}
{
let s = write_fullbox(&mut f, b"iinf", 0, 0);
w16(&mut f, if has_exif { 2 } else { 1 }); {
let si = write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, 1); w16(&mut f, 0); f.extend_from_slice(b"vvc1"); f.push(0); patch(&mut f, si);
}
if has_exif {
let si = write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, 2); w16(&mut f, 0); f.extend_from_slice(b"Exif"); f.push(0); patch(&mut f, si);
}
patch(&mut f, s);
}
if has_exif {
let s = write_fullbox(&mut f, b"iref", 0, 0);
{
let si = write_box(&mut f, b"cdsc");
w16(&mut f, 2); w16(&mut f, 1); w16(&mut f, 1); patch(&mut f, si);
}
patch(&mut f, s);
}
{
let s = write_box(&mut f, b"iprp");
let mut colr2_idx = 0u8;
let mut irot_idx = 0u8;
let mut imir_idx = 0u8;
let mut clli_idx = 0u8;
let clap_idx = if needs_clap { 5 } else { 0 };
{
let si = write_box(&mut f, b"ipco");
{
let sh = write_box(&mut f, b"vvcC");
f.extend_from_slice(&vvcc);
patch(&mut f, sh);
}
if color.cicp.is_some() || color.icc.is_none() {
write_colr_nclx(&mut f, &color.effective_cicp());
} else if let Some(icc) = &color.icc {
write_colr_icc(&mut f, icc);
}
{
let sh = write_fullbox(&mut f, b"ispe", 0, 0);
w32(&mut f, stored_w);
w32(&mut f, stored_h);
patch(&mut f, sh);
}
{
let sh = write_fullbox(&mut f, b"pixi", 0, 0);
f.push(chans);
for _ in 0..chans {
f.push(bd);
}
patch(&mut f, sh);
}
if needs_clap {
write_clap(&mut f, width, height, stored_w, stored_h);
}
let mut next: u8 = if needs_clap { 6 } else { 5 };
if color.has_secondary_colr()
&& let Some(icc) = &color.icc
{
write_colr_icc(&mut f, icc);
colr2_idx = next;
next += 1;
}
if meta.orientation.irot_steps() != 0 {
let sh = write_box(&mut f, b"irot");
f.push(meta.orientation.irot_steps() & 0x03);
patch(&mut f, sh);
irot_idx = next;
next += 1;
}
if let Some(axis) = meta.orientation.imir_axis() {
let sh = write_box(&mut f, b"imir");
f.push(if axis { 1 } else { 0 });
patch(&mut f, sh);
imir_idx = next;
next += 1;
}
if let Some(cll) = meta.content_light_level {
let sh = write_box(&mut f, b"clli");
f.extend_from_slice(&cll.clli_payload());
patch(&mut f, sh);
clli_idx = next;
next += 1;
}
let _ = next;
patch(&mut f, si);
}
{
let mut assoc: Vec<u8> = vec![0x80 | 1, 2, 3, 4]; if clap_idx != 0 {
assoc.push(0x80 | clap_idx);
}
if colr2_idx != 0 {
assoc.push(colr2_idx);
}
if irot_idx != 0 {
assoc.push(0x80 | irot_idx); }
if imir_idx != 0 {
assoc.push(0x80 | imir_idx);
}
if clli_idx != 0 {
assoc.push(clli_idx);
}
let si = write_fullbox(&mut f, b"ipma", 0, 0);
w32(&mut f, 1); w16(&mut f, 1); f.push(assoc.len() as u8);
f.extend_from_slice(&assoc);
patch(&mut f, si);
}
patch(&mut f, s);
}
patch(&mut f, meta_start);
let mdat_start = write_box(&mut f, b"mdat");
let sample_abs = f.len() as u32;
f.extend_from_slice(&sample);
let exif_abs = f.len() as u32;
if has_exif {
f.extend_from_slice(&exif_payload);
}
patch(&mut f, mdat_start);
f[img_offset_patch_pos..img_offset_patch_pos + 4].copy_from_slice(&sample_abs.to_be_bytes());
if has_exif {
f[exif_offset_patch_pos..exif_offset_patch_pos + 4]
.copy_from_slice(&exif_abs.to_be_bytes());
}
Ok(f)
}
#[allow(clippy::type_complexity)]
fn split_for_container(annexb: &[u8]) -> Result<(Vec<&[u8]>, Vec<&[u8]>, Vec<u8>), EncodeError> {
let nals = split_annexb(annexb);
let sps: Vec<&[u8]> = nals
.iter()
.filter(|n| n.nal_type == NUT_SPS)
.map(|n| n.data)
.collect();
let pps: Vec<&[u8]> = nals
.iter()
.filter(|n| n.nal_type == NUT_PPS)
.map(|n| n.data)
.collect();
let vcl: Vec<&[u8]> = nals
.iter()
.filter(|n| n.nal_type <= 11)
.map(|n| n.data)
.collect();
if sps.is_empty() || pps.is_empty() || vcl.is_empty() {
return Err(EncodeError::Unsupported(
"VVC stream missing SPS, PPS or slice data",
));
}
let mut sample = Vec::new();
for &n in &vcl {
w32(&mut sample, n.len() as u32);
sample.extend_from_slice(n);
}
Ok((sps, pps, sample))
}
pub(crate) fn wrap_vvc_still_with_alpha(
master: &[u8],
alpha: &[u8],
width: u32,
height: u32,
bit_depth: BitDepth,
chroma: ChromaFormat,
color: &ColorMetadata,
) -> Result<Vec<u8>, EncodeError> {
const ALPHA_URN: &[u8] = b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha\0";
let chroma = chroma.for_dimensions(width, height);
let (stored_w, stored_h) = stored_extent(width, height, chroma);
let (coded_w, coded_h) = coded_extent(width, height);
let needs_clap = (stored_w, stored_h) != (width, height);
let (m_sps, m_pps, m_sample) = split_for_container(master)?;
let (a_sps, a_pps, a_sample) = split_for_container(alpha)?;
let bd = bit_depth.bits();
let w16cap = coded_w.min(0xffff) as u16;
let h16cap = coded_h.min(0xffff) as u16;
let m_vvcc = build_vvcc(
&m_sps,
&m_pps,
chroma.idc() as u8,
bd,
w16cap,
h16cap,
profile_idc(chroma),
);
let a_vvcc = build_vvcc(&a_sps, &a_pps, 0, bd, w16cap, h16cap, 1);
let m_chans: u8 = if matches!(chroma, ChromaFormat::Monochrome) {
1
} else {
3
};
let mut f: Vec<u8> = Vec::new();
write_ftyp(&mut f);
let meta_start = write_fullbox(&mut f, b"meta", 0, 0);
{
let s = write_fullbox(&mut f, b"hdlr", 0, 0);
w32(&mut f, 0);
f.extend_from_slice(b"pict");
w32(&mut f, 0);
w32(&mut f, 0);
w32(&mut f, 0);
f.push(0);
patch(&mut f, s);
}
{
let s = write_fullbox(&mut f, b"pitm", 0, 0);
w16(&mut f, 1);
patch(&mut f, s);
}
let m_off_patch;
let a_off_patch;
{
let s = write_fullbox(&mut f, b"iloc", 0, 0);
f.push(0x44); f.push(0x00); w16(&mut f, 2);
w16(&mut f, 1);
w16(&mut f, 0);
w16(&mut f, 1);
m_off_patch = f.len();
w32(&mut f, 0);
w32(&mut f, m_sample.len() as u32);
w16(&mut f, 2);
w16(&mut f, 0);
w16(&mut f, 1);
a_off_patch = f.len();
w32(&mut f, 0);
w32(&mut f, a_sample.len() as u32);
patch(&mut f, s);
}
{
let s = write_fullbox(&mut f, b"iinf", 0, 0);
w16(&mut f, 2);
for id in [1u16, 2] {
let si = write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, id);
w16(&mut f, 0);
f.extend_from_slice(b"vvc1");
f.push(0);
patch(&mut f, si);
}
patch(&mut f, s);
}
{
let s = write_fullbox(&mut f, b"iref", 0, 0);
let si = write_box(&mut f, b"auxl");
w16(&mut f, 2); w16(&mut f, 1); w16(&mut f, 1); patch(&mut f, si);
patch(&mut f, s);
}
{
let s = write_box(&mut f, b"iprp");
{
let si = write_box(&mut f, b"ipco");
let sh = write_box(&mut f, b"vvcC");
f.extend_from_slice(&m_vvcc);
patch(&mut f, sh);
if color.cicp.is_some() || color.icc.is_none() {
write_colr_nclx(&mut f, &color.effective_cicp());
} else if let Some(icc) = &color.icc {
write_colr_icc(&mut f, icc);
}
let sh = write_fullbox(&mut f, b"ispe", 0, 0);
w32(&mut f, stored_w);
w32(&mut f, stored_h);
patch(&mut f, sh);
let sh = write_fullbox(&mut f, b"pixi", 0, 0);
f.push(m_chans);
for _ in 0..m_chans {
f.push(bd);
}
patch(&mut f, sh);
let sh = write_box(&mut f, b"vvcC");
f.extend_from_slice(&a_vvcc);
patch(&mut f, sh);
let sh = write_fullbox(&mut f, b"ispe", 0, 0);
w32(&mut f, width);
w32(&mut f, height);
patch(&mut f, sh);
let sh = write_fullbox(&mut f, b"pixi", 0, 0);
f.push(1);
f.push(bd);
patch(&mut f, sh);
let sh = write_fullbox(&mut f, b"auxC", 0, 0);
f.extend_from_slice(ALPHA_URN);
patch(&mut f, sh);
if needs_clap {
write_clap(&mut f, width, height, stored_w, stored_h);
}
patch(&mut f, si);
}
{
let si = write_fullbox(&mut f, b"ipma", 0, 0);
w32(&mut f, 2); w16(&mut f, 1);
f.push(if needs_clap { 5 } else { 4 });
f.extend_from_slice(&[0x80 | 1, 2, 3, 4]);
if needs_clap {
f.push(0x80 | 9);
}
w16(&mut f, 2);
f.push(4);
f.extend_from_slice(&[0x80 | 5, 6, 7, 0x80 | 8]);
patch(&mut f, si);
}
patch(&mut f, s);
}
patch(&mut f, meta_start);
let mdat_start = write_box(&mut f, b"mdat");
let m_abs = f.len() as u32;
f.extend_from_slice(&m_sample);
let a_abs = f.len() as u32;
f.extend_from_slice(&a_sample);
patch(&mut f, mdat_start);
f[m_off_patch..m_off_patch + 4].copy_from_slice(&m_abs.to_be_bytes());
f[a_off_patch..a_off_patch + 4].copy_from_slice(&a_abs.to_be_bytes());
Ok(f)
}
fn rd32(b: &[u8], p: usize) -> Option<u32> {
b.get(p..p + 4)
.map(|s| u32::from_be_bytes([s[0], s[1], s[2], s[3]]))
}
fn rd16(b: &[u8], p: usize) -> Option<u16> {
b.get(p..p + 2).map(|s| u16::from_be_bytes([s[0], s[1]]))
}
fn find_child(
b: &[u8],
start: usize,
end: usize,
fourcc: &[u8; 4],
full: bool,
) -> Option<(usize, usize)> {
let mut p = start;
while p + 8 <= end {
let size = rd32(b, p)? as usize;
let (hdr, box_end) = match size {
1 => (16, p + rd32(b, p + 12)? as usize), 0 => (8, end),
_ => (8, p + size),
};
if box_end > end || box_end <= p {
return None;
}
if &b[p + 4..p + 8] == fourcc {
let ps = p + hdr + if full { 4 } else { 0 };
return Some((ps, box_end));
}
p = box_end;
}
None
}
fn find_nth_child(
b: &[u8],
start: usize,
end: usize,
fourcc: &[u8; 4],
nth: usize,
) -> Option<(usize, usize)> {
let mut p = start;
let mut seen = 0usize;
while p + 8 <= end {
let size = rd32(b, p)? as usize;
let box_end = match size {
1 => p + rd32(b, p + 12)? as usize,
0 => end,
_ => p + size,
};
if box_end > end || box_end <= p {
return None;
}
if &b[p + 4..p + 8] == fourcc {
if seen == nth {
return Some((p + 8, box_end));
}
seen += 1;
}
p = box_end;
}
None
}
fn extract_item(heif: &[u8], want: usize) -> Result<Vec<u8>, EncodeError> {
let n = heif.len();
let err = |m| EncodeError::Decode(m);
let (meta_s, meta_e) = find_child(heif, 0, n, b"meta", true).ok_or(err("HEIF: no meta box"))?;
let (iloc_s, _e) =
find_child(heif, meta_s, meta_e, b"iloc", true).ok_or(err("HEIF: no iloc box"))?;
let sizes = *heif.get(iloc_s).ok_or(err("HEIF: short iloc"))?;
let offset_size = (sizes >> 4) as usize;
let length_size = (sizes & 0xf) as usize;
let base_offset_size = (*heif.get(iloc_s + 1).ok_or(err("HEIF: short iloc"))? >> 4) as usize;
let item_count = rd16(heif, iloc_s + 2).ok_or(err("HEIF: short iloc"))? as usize;
if want >= item_count {
return Err(err("HEIF: item index out of range"));
}
let read_sized = |b: &[u8], at: usize, sz: usize| -> Option<u64> {
let mut v = 0u64;
for i in 0..sz {
v = (v << 8) | *b.get(at + i)? as u64;
}
Some(v)
};
let mut p = iloc_s + 4;
let mut found: Option<(usize, usize)> = None;
for idx in 0..item_count {
p += 2 + 2 + base_offset_size; let extent_count = rd16(heif, p).ok_or(err("HEIF: short iloc extent"))? as usize;
p += 2;
for e in 0..extent_count {
let off =
read_sized(heif, p, offset_size).ok_or(err("HEIF: bad extent offset"))? as usize;
p += offset_size;
let len =
read_sized(heif, p, length_size).ok_or(err("HEIF: bad extent length"))? as usize;
p += length_size;
if idx == want && e == 0 {
found = Some((off, len));
}
}
}
let (ext_off, ext_len) = found.ok_or(err("HEIF: item has no extent"))?;
let end = ext_off
.checked_add(ext_len)
.ok_or(err("HEIF: extent overflow"))?;
if end > n {
return Err(err("HEIF: extent overruns file"));
}
let sample = &heif[ext_off..end];
let (iprp_s, iprp_e) =
find_child(heif, meta_s, meta_e, b"iprp", false).ok_or(err("HEIF: no iprp box"))?;
let (ipco_s, ipco_e) =
find_child(heif, iprp_s, iprp_e, b"ipco", false).ok_or(err("HEIF: no ipco box"))?;
let (vvcc_s, vvcc_e) =
find_nth_child(heif, ipco_s, ipco_e, b"vvcC", want).ok_or(err("HEIF: no vvcC for item"))?;
let (sps_nals, pps_nals) = read_vvcc_arrays(&heif[vvcc_s..vvcc_e])?;
if sps_nals.is_empty() || pps_nals.is_empty() {
return Err(err("HEIF: vvcC missing SPS/PPS"));
}
let start = [0u8, 0, 0, 1];
let mut out = Vec::with_capacity(ext_len + 64);
for nal in sps_nals.iter().chain(pps_nals.iter()) {
out.extend_from_slice(&start);
out.extend_from_slice(nal);
}
let mut q = 0usize;
while q + 4 <= sample.len() {
let ln = rd32(sample, q).ok_or(err("HEIF: bad NAL length"))? as usize;
q += 4;
if q + ln > sample.len() {
return Err(err("HEIF: NAL length overruns sample"));
}
out.extend_from_slice(&start);
out.extend_from_slice(&sample[q..q + ln]);
q += ln;
}
Ok(out)
}
pub(crate) fn extract_vvc_stream(heif: &[u8]) -> Result<Vec<u8>, EncodeError> {
extract_item(heif, 0)
}
pub(crate) fn extract_alpha_stream(heif: &[u8]) -> Option<Vec<u8>> {
extract_item(heif, 1).ok()
}
pub(crate) fn extract_spatial_extents(heif: &[u8], want: usize) -> Option<(u32, u32)> {
let (meta_s, meta_e) = find_child(heif, 0, heif.len(), b"meta", true)?;
let (iprp_s, iprp_e) = find_child(heif, meta_s, meta_e, b"iprp", false)?;
let (ipco_s, ipco_e) = find_child(heif, iprp_s, iprp_e, b"ipco", false)?;
let (ispe_s, ispe_e) = find_nth_child(heif, ipco_s, ipco_e, b"ispe", want)?;
if ispe_s + 12 > ispe_e {
return None;
}
let stored = (rd32(heif, ispe_s + 4)?, rd32(heif, ispe_s + 8)?);
if want != 0 {
return Some(stored);
}
let (clap_s, clap_e) = match find_child(heif, ipco_s, ipco_e, b"clap", false) {
Some(range) => range,
None => return Some(stored),
};
if clap_s + 32 > clap_e {
return None;
}
let (wn, wd) = (rd32(heif, clap_s)?, rd32(heif, clap_s + 4)?);
let (hn, hd) = (rd32(heif, clap_s + 8)?, rd32(heif, clap_s + 12)?);
if wd == 0 || hd == 0 {
return None;
}
let display = (wn.div_ceil(wd), hn.div_ceil(hd));
if display.0 > stored.0 || display.1 > stored.1 {
return None;
}
Some(display)
}
pub(crate) fn extract_metadata(heif: &[u8]) -> (Orientation, ColorMetadata) {
let mut steps = 0u8;
let mut axis: Option<bool> = None;
let mut color = ColorMetadata::default();
let n = heif.len();
if let Some((meta_s, meta_e)) = find_child(heif, 0, n, b"meta", true)
&& let Some((iprp_s, iprp_e)) = find_child(heif, meta_s, meta_e, b"iprp", false)
&& let Some((ipco_s, ipco_e)) = find_child(heif, iprp_s, iprp_e, b"ipco", false)
{
if let Some((s, _)) = find_child(heif, ipco_s, ipco_e, b"irot", false)
&& let Some(&b) = heif.get(s)
{
steps = b & 3;
}
if let Some((s, _)) = find_child(heif, ipco_s, ipco_e, b"imir", false)
&& let Some(&b) = heif.get(s)
{
axis = Some(b & 1 == 1);
}
let mut i = 0;
while let Some((s, e)) = find_nth_child(heif, ipco_s, ipco_e, b"colr", i) {
match heif.get(s..s + 4) {
Some(b"nclx") => color.cicp = Cicp::from_nclx_payload(&heif[s..e]),
Some(b"prof") | Some(b"rICC") => color.icc = Some(heif[s + 4..e].to_vec()),
_ => {}
}
i += 1;
}
}
(Orientation::from_irot_imir(steps, axis), color)
}
#[allow(clippy::type_complexity)]
fn read_vvcc_arrays(v: &[u8]) -> Result<(Vec<Vec<u8>>, Vec<Vec<u8>>), EncodeError> {
let err = |m| EncodeError::Decode(m);
let mut p = 0usize;
let b0 = *v.first().ok_or(err("vvcC: empty"))?;
p += 1;
let ptl_present = b0 & 1;
if ptl_present == 1 {
p += 2; p += 1; let num_bytes_ci = (*v.get(p).ok_or(err("vvcC: short"))? & 0x3f) as usize;
p += 1;
p += 1; p += 1; p += num_bytes_ci; let nsp = *v.get(p).ok_or(err("vvcC: short"))? as usize;
p += 1;
p += 4 * nsp;
p += 6; }
let num_arrays = *v.get(p).ok_or(err("vvcC: short"))?;
p += 1;
let (mut sps, mut pps) = (Vec::new(), Vec::new());
for _ in 0..num_arrays {
let hdr = *v.get(p).ok_or(err("vvcC: short array"))?;
p += 1;
let nut = hdr & 0x1f;
let num_nalus = rd16(v, p).ok_or(err("vvcC: short array"))?;
p += 2;
for _ in 0..num_nalus {
let ln = rd16(v, p).ok_or(err("vvcC: short nalu"))? as usize;
p += 2;
let nal = v.get(p..p + ln).ok_or(err("vvcC: nalu overrun"))?.to_vec();
p += ln;
match nut {
15 => sps.push(nal),
16 => pps.push(nal),
_ => {}
}
}
}
Ok((sps, pps))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn splits_annexb_into_nals() {
let mut s = vec![0, 0, 0, 1, 0x00, 0x78, 0xaa]; s.extend_from_slice(&[0, 0, 1, 0x00, 0x80, 0xbb]); let nals = split_annexb(&s);
assert_eq!(nals.len(), 2);
assert_eq!(nals[0].nal_type, 15);
assert_eq!(nals[1].nal_type, 16);
}
#[test]
fn vvcc_starts_with_expected_fixed_bytes() {
let sps: Vec<&[u8]> = vec![&[0x00, 0x78, 0x11]];
let pps: Vec<&[u8]> = vec![&[0x00, 0x80, 0x22]];
let v = build_vvcc(&sps, &pps, 1, 8, 64, 64, 1);
assert_eq!(v[0], 0xff); assert_eq!(&v[1..3], &[0x00, 0x15]); assert_eq!(v[3], 0x1f); assert_eq!(v[4], 0x01); assert_eq!(v[5], 0x02); assert_eq!(v[6], 102); assert_eq!(v[7], 0x80); }
#[test]
fn odd_420_uses_exact_promoted_extent() {
let mut annexb = Vec::new();
for second in [0x78, 0x80, 0x00] {
annexb.extend_from_slice(&[0, 0, 0, 1, 1, second, 1]);
}
let heif = wrap_vvc_still(
&annexb,
9,
11,
BitDepth::Eight,
ChromaFormat::Yuv420,
&ColorMetadata::default(),
&ImageMetadata::default(),
)
.unwrap();
let (meta_s, meta_e) = find_child(&heif, 0, heif.len(), b"meta", true).unwrap();
let (iprp_s, iprp_e) = find_child(&heif, meta_s, meta_e, b"iprp", false).unwrap();
let (ipco_s, ipco_e) = find_child(&heif, iprp_s, iprp_e, b"ipco", false).unwrap();
let (ispe_s, _) = find_child(&heif, ipco_s, ipco_e, b"ispe", false).unwrap();
assert_eq!(
(rd32(&heif, ispe_s + 4), rd32(&heif, ispe_s + 8)),
(Some(9), Some(11))
);
assert!(find_child(&heif, ipco_s, ipco_e, b"clap", false).is_none());
assert_eq!(extract_spatial_extents(&heif, 0), Some((9, 11)));
let (ipma_s, _) = find_child(&heif, iprp_s, iprp_e, b"ipma", true).unwrap();
assert_eq!(heif[ipma_s + 6], 4);
assert_eq!(&heif[ipma_s + 7..ipma_s + 11], &[0x81, 2, 3, 4]);
}
}