use crate::{error::EncodeError, hevc::NaluStream};
#[derive(Clone, Copy)]
pub(crate) struct ImageMeta<'a> {
pub(crate) bit_depth: crate::fmt::BitDepth,
pub(crate) color_meta: &'a crate::color::ColorMetadata,
pub(crate) metadata: &'a crate::metadata::Metadata,
}
#[derive(Clone, Copy)]
pub(crate) struct GridDims {
pub(crate) cols: u32,
pub(crate) rows: u32,
pub(crate) tile_w: u32,
pub(crate) tile_h: u32,
pub(crate) full_w: u32,
pub(crate) full_h: u32,
}
fn epb(nalu: &[u8]) -> Vec<u8> {
if nalu.len() <= 2 {
return nalu.to_vec();
}
let mut out = Vec::with_capacity(nalu.len() + 4);
out.push(nalu[0]);
out.push(nalu[1]); let mut zeros: u8 = 0;
for &b in &nalu[2..] {
if zeros >= 2 && b <= 3 {
out.push(0x03);
zeros = 0;
}
zeros = if b == 0 { zeros + 1 } else { 0 };
out.push(b);
}
out
}
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_fullbox(buf: &mut Vec<u8>, cc: &[u8; 4], ver: u8, flags: u32) {
w32(buf, 0);
buf.extend_from_slice(cc);
buf.push(ver);
buf.push((flags >> 16) as u8);
buf.push((flags >> 8) as u8);
buf.push(flags as u8);
}
fn write_box(buf: &mut Vec<u8>, cc: &[u8; 4]) {
w32(buf, 0);
buf.extend_from_slice(cc);
}
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 write_colr(f: &mut Vec<u8>, color: &crate::color::ColorMetadata) {
if let Some(enc) = color.cicp.as_ref() {
write_colr_nclx(f, enc);
} else if let Some(icc) = color.icc.as_ref() {
write_colr_icc(f, icc);
} else {
write_colr_nclx(f, &crate::color::Cicp::unspecified());
}
}
fn write_colr_nclx(f: &mut Vec<u8>, enc: &crate::color::Cicp) {
let sh = f.len();
write_box(f, b"colr");
f.extend_from_slice(&enc.nclx_payload());
patch(f, sh);
}
fn write_colr_icc(f: &mut Vec<u8>, icc: &[u8]) {
let sh = f.len();
write_box(f, b"colr");
f.extend_from_slice(b"prof");
f.extend_from_slice(icc);
patch(f, sh);
}
fn has_secondary_colr(color: &crate::color::ColorMetadata) -> bool {
color.cicp.is_some() && color.icc.is_some()
}
fn write_secondary_colr(f: &mut Vec<u8>, color: &crate::color::ColorMetadata) {
if let (Some(_), Some(icc)) = (color.cicp.as_ref(), color.icc.as_ref()) {
write_colr_icc(f, icc);
}
}
fn write_ftyp(f: &mut Vec<u8>, rext: bool) {
let brand: &[u8; 4] = if rext { b"heix" } else { b"heic" };
let s = f.len();
write_box(f, b"ftyp");
f.extend_from_slice(brand); w32(f, 0); f.extend_from_slice(brand); f.extend_from_slice(b"mif1");
f.extend_from_slice(b"miaf");
patch(f, s);
}
#[inline]
fn hvcc_uses_rext(hvcc: &[u8]) -> bool {
matches!(hvcc.get(1), Some(profile) if *profile & 0x1f == 4)
}
fn write_iloc_item(f: &mut Vec<u8>, item_id: u16, data_len: u32) -> usize {
w16(f, item_id);
w16(f, 0); w32(f, 0); w16(f, 1); let patch_pos = f.len();
w32(f, 0); w32(f, data_len);
patch_pos
}
pub(crate) fn wrap_hevc_image_with_alpha(
color: &NaluStream,
alpha: &NaluStream,
width: u32,
height: u32,
img: ImageMeta<'_>,
) -> Result<Vec<u8>, EncodeError> {
let ImageMeta {
bit_depth,
color_meta,
metadata,
} = img;
let color_sample = color.to_length_prefixed_slices();
let alpha_sample = alpha.to_length_prefixed_slices();
let color_hvcc = build_hvcc(color, bit_depth.bits())?;
let alpha_hvcc = build_hvcc(alpha, bit_depth.bits())?;
const ALPHA_URN: &[u8] = b"urn:mpeg:hevc:2015:auxid:1\0";
let mut f: Vec<u8> = Vec::new();
write_ftyp(&mut f, hvcc_uses_rext(&color_hvcc));
let meta_start = f.len();
write_fullbox(&mut f, b"meta", 0, 0);
{
let s = f.len();
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 = f.len();
write_fullbox(&mut f, b"pitm", 0, 0);
w16(&mut f, 1);
patch(&mut f, s);
}
let color_offset_patch_pos;
let alpha_offset_patch_pos;
{
let s = f.len();
write_fullbox(&mut f, b"iloc", 0, 0);
f.push(0x44); f.push(0x40); w16(&mut f, 2);
color_offset_patch_pos = write_iloc_item(&mut f, 1, color_sample.len() as u32);
alpha_offset_patch_pos = write_iloc_item(&mut f, 2, alpha_sample.len() as u32);
patch(&mut f, s);
}
{
let s = f.len();
write_fullbox(&mut f, b"iinf", 0, 0);
w16(&mut f, 2);
for id in [1u16, 2u16] {
let si = f.len();
write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, id);
w16(&mut f, 0);
f.extend_from_slice(b"hvc1");
f.push(0);
patch(&mut f, si);
}
patch(&mut f, s);
}
{
let s = f.len();
write_fullbox(&mut f, b"iref", 0, 0);
{
let sr = f.len();
write_box(&mut f, b"auxl");
w16(&mut f, 2);
w16(&mut f, 1);
w16(&mut f, 1);
patch(&mut f, sr);
}
patch(&mut f, s);
}
{
let s = f.len();
write_box(&mut f, b"iprp");
let mut irot_idx = 0u8;
let mut imir_idx = 0u8;
let mut clli_idx = 0u8;
let mut colr2_idx = 0u8;
{
let si = f.len();
write_box(&mut f, b"ipco");
{
let sh = f.len();
write_box(&mut f, b"hvcC");
f.extend_from_slice(&color_hvcc);
patch(&mut f, sh);
}
write_colr(&mut f, color_meta);
{
let sh = f.len();
write_fullbox(&mut f, b"ispe", 0, 0);
w32(&mut f, width);
w32(&mut f, height);
patch(&mut f, sh);
}
{
let sh = f.len();
write_fullbox(&mut f, b"pixi", 0, 0);
f.push(3);
f.push(bit_depth.bits());
f.push(bit_depth.bits());
f.push(bit_depth.bits());
patch(&mut f, sh);
}
{
let sh = f.len();
write_box(&mut f, b"hvcC");
f.extend_from_slice(&alpha_hvcc);
patch(&mut f, sh);
}
{
let sh = f.len();
write_fullbox(&mut f, b"pixi", 0, 0);
f.push(1);
f.push(bit_depth.bits());
patch(&mut f, sh);
}
{
let sh = f.len();
write_fullbox(&mut f, b"auxC", 0, 0);
f.extend_from_slice(ALPHA_URN);
patch(&mut f, sh);
}
let mut next_prop: u8 = 8;
if has_secondary_colr(color_meta) {
write_secondary_colr(&mut f, color_meta);
colr2_idx = next_prop;
next_prop += 1;
}
if metadata.orientation.irot_steps() != 0 {
let sh = f.len();
write_box(&mut f, b"irot");
f.push(metadata.orientation.irot_steps() & 0x03);
patch(&mut f, sh);
irot_idx = next_prop;
next_prop += 1;
}
if let Some(ax) = metadata.orientation.imir_axis() {
let sh = f.len();
write_box(&mut f, b"imir");
f.push(if ax { 1 } else { 0 });
patch(&mut f, sh);
imir_idx = next_prop;
next_prop += 1;
}
if let Some(cll) = metadata.content_light_level {
let sh = f.len();
write_box(&mut f, b"clli");
f.extend_from_slice(&cll.clli_payload());
patch(&mut f, sh);
clli_idx = next_prop;
next_prop += 1;
}
let _ = next_prop;
patch(&mut f, si);
}
{
let si = f.len();
write_fullbox(&mut f, b"ipma", 0, 0);
w32(&mut f, 2);
let mut ca: Vec<u8> = vec![0x80 | 1, 2, 3, 4];
if colr2_idx != 0 {
ca.push(colr2_idx);
}
if irot_idx != 0 {
ca.push(0x80 | irot_idx);
}
if imir_idx != 0 {
ca.push(0x80 | imir_idx);
}
if clli_idx != 0 {
ca.push(clli_idx);
}
w16(&mut f, 1);
f.push(ca.len() as u8);
f.extend_from_slice(&ca);
w16(&mut f, 2);
f.push(4);
f.push(0x80 | 5);
f.push(3);
f.push(6);
f.push(7);
patch(&mut f, si);
}
patch(&mut f, s);
}
patch(&mut f, meta_start);
let mdat_start = f.len();
write_box(&mut f, b"mdat");
let color_abs = f.len() as u32;
f.extend_from_slice(&color_sample);
let alpha_abs = f.len() as u32;
f.extend_from_slice(&alpha_sample);
patch(&mut f, mdat_start);
f[color_offset_patch_pos..color_offset_patch_pos + 4].copy_from_slice(&color_abs.to_be_bytes());
f[alpha_offset_patch_pos..alpha_offset_patch_pos + 4].copy_from_slice(&alpha_abs.to_be_bytes());
Ok(f)
}
pub(crate) fn wrap_hevc_image(
stream: &NaluStream,
width: u32,
height: u32,
img: ImageMeta<'_>,
) -> Result<Vec<u8>, EncodeError> {
let ImageMeta {
bit_depth,
color_meta,
metadata,
} = img;
let hevc_sample = stream.to_length_prefixed_slices();
let hvcc_data = build_hvcc(stream, bit_depth.bits())?;
let mut f: Vec<u8> = Vec::new();
write_ftyp(&mut f, hvcc_uses_rext(&hvcc_data));
let meta_start = f.len();
write_fullbox(&mut f, b"meta", 0, 0);
{
let s = f.len();
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 = f.len();
write_fullbox(&mut f, b"pitm", 0, 0);
w16(&mut f, 1);
patch(&mut f, s);
}
let has_exif = metadata.exif.is_some();
let exif_payload: Vec<u8> = metadata
.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 iloc_offset_patch_pos;
let mut iloc_exif_patch_pos = 0usize;
{
let s = f.len();
write_fullbox(&mut f, b"iloc", 0, 0);
f.push(0x44); f.push(0x40); w16(&mut f, if has_exif { 2 } else { 1 });
iloc_offset_patch_pos = write_iloc_item(&mut f, 1, hevc_sample.len() as u32);
if has_exif {
iloc_exif_patch_pos = write_iloc_item(&mut f, 2, exif_payload.len() as u32);
}
patch(&mut f, s);
}
{
let s = f.len();
write_fullbox(&mut f, b"iinf", 0, 0);
w16(&mut f, if has_exif { 2 } else { 1 });
{
let si = f.len();
write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, 1);
w16(&mut f, 0);
f.extend_from_slice(b"hvc1");
f.push(0);
patch(&mut f, si);
}
if has_exif {
let si = f.len();
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 = f.len();
write_fullbox(&mut f, b"iref", 0, 0);
{
let si = f.len();
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 extra_props: (u8, u8, u8, u8);
let s = f.len();
write_box(&mut f, b"iprp");
{
let si = f.len();
write_box(&mut f, b"ipco");
{
let sh = f.len();
write_box(&mut f, b"hvcC");
f.extend_from_slice(&hvcc_data);
patch(&mut f, sh);
}
write_colr(&mut f, color_meta);
{
let sh = f.len();
write_fullbox(&mut f, b"ispe", 0, 0);
w32(&mut f, width);
w32(&mut f, height);
patch(&mut f, sh);
}
{
let sh = f.len();
write_fullbox(&mut f, b"pixi", 0, 0);
f.push(3);
f.push(bit_depth.bits());
f.push(bit_depth.bits());
f.push(bit_depth.bits());
patch(&mut f, sh);
}
let mut next_prop: u8 = 5;
let mut irot_idx = 0u8;
let mut imir_idx = 0u8;
let mut clli_idx = 0u8;
let mut colr2_idx = 0u8;
if has_secondary_colr(color_meta) {
write_secondary_colr(&mut f, color_meta);
colr2_idx = next_prop;
next_prop += 1;
}
if metadata.orientation.irot_steps() != 0 {
let sh = f.len();
write_box(&mut f, b"irot");
f.push(metadata.orientation.irot_steps() & 0x03);
patch(&mut f, sh);
irot_idx = next_prop;
next_prop += 1;
}
if let Some(ax) = metadata.orientation.imir_axis() {
let sh = f.len();
write_box(&mut f, b"imir");
f.push(if ax { 1 } else { 0 });
patch(&mut f, sh);
imir_idx = next_prop;
next_prop += 1;
}
if let Some(cll) = metadata.content_light_level {
let sh = f.len();
write_box(&mut f, b"clli");
f.extend_from_slice(&cll.clli_payload());
patch(&mut f, sh);
clli_idx = next_prop;
next_prop += 1;
}
let _ = next_prop;
extra_props = (irot_idx, imir_idx, clli_idx, colr2_idx);
patch(&mut f, si);
}
{
let (irot_idx, imir_idx, clli_idx, colr2_idx) = extra_props;
let mut assoc: Vec<u8> = vec![0x80 | 1, 2, 3, 4];
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 = f.len();
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 = f.len();
write_box(&mut f, b"mdat");
let hevc_abs = f.len() as u32;
f.extend_from_slice(&hevc_sample);
let exif_abs = f.len() as u32;
if has_exif {
f.extend_from_slice(&exif_payload);
}
patch(&mut f, mdat_start);
f[iloc_offset_patch_pos..iloc_offset_patch_pos + 4].copy_from_slice(&hevc_abs.to_be_bytes());
if has_exif {
f[iloc_exif_patch_pos..iloc_exif_patch_pos + 4].copy_from_slice(&exif_abs.to_be_bytes());
}
Ok(f)
}
fn sps_chroma_format_idc(stream: &NaluStream) -> Option<u8> {
let sps = stream
.nalus
.iter()
.find(|n| (n.data[0] >> 1) & 0x3f == 33)
.map(|n| n.data.as_slice())?;
let start_bit = 15 * 8;
let mut pos = start_bit;
let get_bit = |p: usize| -> u32 {
if p / 8 >= sps.len() {
return 0;
}
((sps[p / 8] >> (7 - (p % 8))) & 1) as u32
};
let read_ue = |pos: &mut usize| -> u32 {
let mut zeros = 0;
while *pos < sps.len() * 8 && get_bit(*pos) == 0 {
zeros += 1;
*pos += 1;
}
*pos += 1;
if zeros == 0 {
return 0;
}
let mut val = 0u32;
for _ in 0..zeros {
val = (val << 1) | get_bit(*pos);
*pos += 1;
}
(1 << zeros) - 1 + val
};
let _sps_id = read_ue(&mut pos);
Some(read_ue(&mut pos) as u8)
}
fn build_hvcc(stream: &NaluStream, bit_depth: u8) -> Result<Vec<u8>, EncodeError> {
let mut vps: Vec<&[u8]> = Vec::new();
let mut sps: Vec<&[u8]> = Vec::new();
let mut pps: Vec<&[u8]> = Vec::new();
for nalu in &stream.nalus {
match (nalu.data[0] >> 1) & 0x3f {
32 => vps.push(&nalu.data),
33 => sps.push(&nalu.data),
34 => pps.push(&nalu.data),
_ => {}
}
}
let mut r: Vec<u8> = Vec::new();
r.push(1); let (profile_byte, compat, constraint6, level) = if let Some(s) = sps.first() {
if s.len() >= 15 {
let mut cp = [0u8; 4];
cp.copy_from_slice(&s[4..8]);
let mut c = [0u8; 6];
c.copy_from_slice(&s[8..14]);
(s[3], cp, c, s[14])
} else {
(0b0000_0100, [0x00, 0, 0, 0], [0x90, 0, 0, 0, 0, 0], 93)
}
} else {
(0b0000_0100, [0x00, 0, 0, 0], [0x90, 0, 0, 0, 0, 0], 93)
};
r.push(profile_byte);
r.extend_from_slice(&compat);
r.extend_from_slice(&constraint6);
r.push(level);
r.push(0xF0);
r.push(0x00); r.push(0xFC); let chroma_idc = sps_chroma_format_idc(stream).unwrap_or(1);
r.push(0xFC | (chroma_idc & 0x3));
r.push(0xF8 | (bit_depth - 8)); r.push(0xF8 | (bit_depth - 8)); r.push(0x00);
r.push(0x00); r.push(0b0000_1111);
let arrays: &[(u8, &Vec<&[u8]>)] = &[(32, &vps), (33, &sps), (34, &pps)];
r.push(arrays.len() as u8);
for &(nal_type, list) in arrays {
r.push(nal_type); r.push((list.len() >> 8) as u8);
r.push(list.len() as u8);
for &d in list {
let ebsp = epb(d);
r.push((ebsp.len() >> 8) as u8);
r.push(ebsp.len() as u8);
r.extend_from_slice(&ebsp);
}
}
Ok(r)
}
pub(crate) fn wrap_hevc_grid(
tiles: &[NaluStream],
dims: GridDims,
img: ImageMeta<'_>,
) -> Result<Vec<u8>, EncodeError> {
let GridDims {
cols,
rows,
tile_w,
tile_h,
full_w,
full_h,
} = dims;
let ImageMeta {
bit_depth,
color_meta,
metadata,
} = img;
assert_eq!(
tiles.len(),
(cols * rows) as usize,
"tile count must equal cols*rows"
);
let hvcc_data = build_hvcc(&tiles[0], bit_depth.bits())?;
let tile_samples: Vec<Vec<u8>> = tiles
.iter()
.map(|t| t.to_length_prefixed_slices())
.collect();
let use_32bit = full_w > 65535 || full_h > 65535;
let grid_payload: Vec<u8> = {
let flags: u8 = if use_32bit { 2 } else { 0 }; let mut g = vec![0u8, flags, (rows - 1) as u8, (cols - 1) as u8];
if use_32bit {
g.extend_from_slice(&full_w.to_be_bytes()); g.extend_from_slice(&full_h.to_be_bytes()); } else {
g.extend_from_slice(&(full_w as u16).to_be_bytes()); g.extend_from_slice(&(full_h as u16).to_be_bytes()); }
g
};
let n_tiles = tiles.len() as u16; let grid_id = n_tiles + 1; let has_exif = metadata.exif.is_some();
let exif_id = grid_id + 1;
let exif_payload: Vec<u8> = metadata
.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 mut f: Vec<u8> = Vec::new();
write_ftyp(&mut f, hvcc_uses_rext(&hvcc_data));
let meta_start = f.len();
write_fullbox(&mut f, b"meta", 0, 0);
{
let s = f.len();
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 = f.len();
write_fullbox(&mut f, b"pitm", 0, 0);
w16(&mut f, grid_id);
patch(&mut f, s);
}
let mut tile_patch_positions: Vec<usize> = Vec::with_capacity(n_tiles as usize);
let mut exif_patch_pos = 0usize;
{
let s = f.len();
write_fullbox(&mut f, b"iloc", 1, 0);
f.push(0x44); f.push(0x00); let total_items = n_tiles + 1 + if has_exif { 1 } else { 0 };
w16(&mut f, total_items);
for i in 0..n_tiles {
w16(&mut f, i + 1); w16(&mut f, 0); w16(&mut f, 0); w16(&mut f, 1); let pp = f.len();
w32(&mut f, 0); w32(&mut f, tile_samples[i as usize].len() as u32);
tile_patch_positions.push(pp);
}
w16(&mut f, grid_id);
w16(&mut f, 1); w16(&mut f, 0); w16(&mut f, 1); w32(&mut f, 0); w32(&mut f, grid_payload.len() as u32);
if has_exif {
w16(&mut f, exif_id);
w16(&mut f, 0); w16(&mut f, 0);
w16(&mut f, 1);
exif_patch_pos = f.len();
w32(&mut f, 0);
w32(&mut f, exif_payload.len() as u32);
}
patch(&mut f, s);
}
{
let s = f.len();
write_fullbox(&mut f, b"iinf", 0, 0);
let entry_count = n_tiles + 1 + if has_exif { 1 } else { 0 };
w16(&mut f, entry_count);
for i in 0..n_tiles {
let si = f.len();
w32(&mut f, 0); f.extend_from_slice(b"infe");
f.push(2u8); f.push(0u8); f.push(0u8); f.push(1u8); w16(&mut f, i + 1); w16(&mut f, 0); f.extend_from_slice(b"hvc1"); f.push(0); patch(&mut f, si);
}
{
let si = f.len();
write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, grid_id);
w16(&mut f, 0);
f.extend_from_slice(b"grid");
f.push(0);
patch(&mut f, si);
}
if has_exif {
let si = f.len();
write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, exif_id);
w16(&mut f, 0);
f.extend_from_slice(b"Exif");
f.push(0);
patch(&mut f, si);
}
patch(&mut f, s);
}
{
let s = f.len();
write_fullbox(&mut f, b"iref", 0, 0);
{
let sr = f.len();
write_box(&mut f, b"dimg");
w16(&mut f, grid_id); w16(&mut f, n_tiles); for i in 1..=n_tiles {
w16(&mut f, i);
}
patch(&mut f, sr);
}
if has_exif {
let sr = f.len();
write_box(&mut f, b"cdsc");
w16(&mut f, exif_id);
w16(&mut f, 1);
w16(&mut f, grid_id);
patch(&mut f, sr);
}
patch(&mut f, s);
}
{
let s = f.len();
write_box(&mut f, b"iprp");
let mut irot_idx = 0u8;
let mut imir_idx = 0u8;
let mut clli_idx = 0u8;
let mut colr2_idx = 0u8;
{
let si = f.len();
write_box(&mut f, b"ipco");
{
let sh = f.len();
write_box(&mut f, b"hvcC");
f.extend_from_slice(&hvcc_data);
patch(&mut f, sh);
}
{
let sh = f.len();
write_fullbox(&mut f, b"ispe", 0, 0);
w32(&mut f, tile_w);
w32(&mut f, tile_h);
patch(&mut f, sh);
}
{
let sh = f.len();
write_fullbox(&mut f, b"ispe", 0, 0);
w32(&mut f, full_w);
w32(&mut f, full_h);
patch(&mut f, sh);
}
write_colr(&mut f, color_meta);
{
let sh = f.len();
write_fullbox(&mut f, b"pixi", 0, 0);
f.push(3);
f.push(bit_depth.bits());
f.push(bit_depth.bits());
f.push(bit_depth.bits());
patch(&mut f, sh);
}
let mut next: u8 = 6;
if has_secondary_colr(color_meta) {
write_secondary_colr(&mut f, color_meta);
colr2_idx = next;
next += 1;
}
if metadata.orientation.irot_steps() != 0 {
let sh = f.len();
write_box(&mut f, b"irot");
f.push(metadata.orientation.irot_steps() & 3);
patch(&mut f, sh);
irot_idx = next;
next += 1;
}
if let Some(ax) = metadata.orientation.imir_axis() {
let sh = f.len();
write_box(&mut f, b"imir");
f.push(if ax { 1 } else { 0 });
patch(&mut f, sh);
imir_idx = next;
next += 1;
}
if let Some(cll) = metadata.content_light_level {
let sh = f.len();
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 si = f.len();
write_fullbox(&mut f, b"ipma", 0, 0);
let entry_count = n_tiles + 1; w32(&mut f, entry_count as u32);
for i in 1..=n_tiles {
w16(&mut f, i);
f.push(3);
f.push(0x80 | 1); f.push(0x80 | 2); f.push(0x80 | 4); }
w16(&mut f, grid_id);
let mut ga: Vec<u8> = vec![3, 0x80 | 4, 5]; if colr2_idx != 0 {
ga.push(colr2_idx); }
if irot_idx != 0 {
ga.push(0x80 | irot_idx);
}
if imir_idx != 0 {
ga.push(0x80 | imir_idx);
}
if clli_idx != 0 {
ga.push(clli_idx);
}
f.push(ga.len() as u8);
f.extend_from_slice(&ga);
patch(&mut f, si);
}
patch(&mut f, s);
}
{
let s = f.len();
write_box(&mut f, b"idat");
f.extend_from_slice(&grid_payload);
patch(&mut f, s);
}
patch(&mut f, meta_start);
let mdat_start = f.len();
write_box(&mut f, b"mdat");
let mut tile_abs: Vec<u32> = Vec::with_capacity(n_tiles as usize);
for sample in &tile_samples {
tile_abs.push(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);
for (i, &abs) in tile_abs.iter().enumerate() {
let pp = tile_patch_positions[i];
f[pp..pp + 4].copy_from_slice(&abs.to_be_bytes());
}
if has_exif {
f[exif_patch_pos..exif_patch_pos + 4].copy_from_slice(&exif_abs.to_be_bytes());
}
Ok(f)
}
pub(crate) fn wrap_hevc_grid_with_alpha(
color_tiles: &[NaluStream],
alpha_tiles: &[NaluStream],
dims: GridDims,
img: ImageMeta<'_>,
) -> Result<Vec<u8>, EncodeError> {
let GridDims {
cols,
rows,
tile_w,
tile_h,
full_w,
full_h,
} = dims;
let ImageMeta {
bit_depth,
color_meta,
metadata,
} = img;
assert_eq!(color_tiles.len(), (cols * rows) as usize);
assert_eq!(alpha_tiles.len(), (cols * rows) as usize);
const ALPHA_URN: &[u8] = b"urn:mpeg:hevc:2015:auxid:1\0";
let color_hvcc = build_hvcc(&color_tiles[0], bit_depth.bits())?;
let alpha_hvcc = build_hvcc(&alpha_tiles[0], bit_depth.bits())?;
let color_samples: Vec<Vec<u8>> = color_tiles
.iter()
.map(|t| t.to_length_prefixed_slices())
.collect();
let alpha_samples: Vec<Vec<u8>> = alpha_tiles
.iter()
.map(|t| t.to_length_prefixed_slices())
.collect();
let use_32bit = full_w > 65535 || full_h > 65535;
let grid_payload: Vec<u8> = {
let flags: u8 = if use_32bit { 2 } else { 0 };
let mut g = vec![0u8, flags, (rows - 1) as u8, (cols - 1) as u8];
if use_32bit {
g.extend_from_slice(&full_w.to_be_bytes());
g.extend_from_slice(&full_h.to_be_bytes());
} else {
g.extend_from_slice(&(full_w as u16).to_be_bytes());
g.extend_from_slice(&(full_h as u16).to_be_bytes());
}
g
};
let alpha_grid_offset = grid_payload.len() as u32;
let n = color_tiles.len() as u16; let color_grid_id = n + 1;
let alpha_tile_base = n + 2; let alpha_grid_id = alpha_tile_base + n;
let has_exif = metadata.exif.is_some();
let exif_id = alpha_grid_id + 1;
let exif_payload: Vec<u8> = metadata
.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 mut f: Vec<u8> = Vec::new();
write_ftyp(&mut f, hvcc_uses_rext(&color_hvcc));
let meta_start = f.len();
write_fullbox(&mut f, b"meta", 0, 0);
{
let s = f.len();
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 = f.len();
write_fullbox(&mut f, b"pitm", 0, 0);
w16(&mut f, color_grid_id);
patch(&mut f, s);
}
let mut color_tile_patches: Vec<usize> = Vec::with_capacity(n as usize);
let mut alpha_tile_patches: Vec<usize> = Vec::with_capacity(n as usize);
let mut exif_patch_pos = 0usize;
{
let s = f.len();
write_fullbox(&mut f, b"iloc", 1, 0);
f.push(0x44); f.push(0x00); let total = 2 * n + 2 + if has_exif { 1 } else { 0 };
w16(&mut f, total);
for i in 0..n {
w16(&mut f, i + 1);
w16(&mut f, 0);
w16(&mut f, 0);
w16(&mut f, 1);
let pp = f.len();
w32(&mut f, 0);
w32(&mut f, color_samples[i as usize].len() as u32);
color_tile_patches.push(pp);
}
w16(&mut f, color_grid_id);
w16(&mut f, 1);
w16(&mut f, 0);
w16(&mut f, 1);
w32(&mut f, 0);
w32(&mut f, grid_payload.len() as u32);
for i in 0..n {
w16(&mut f, alpha_tile_base + i);
w16(&mut f, 0);
w16(&mut f, 0);
w16(&mut f, 1);
let pp = f.len();
w32(&mut f, 0);
w32(&mut f, alpha_samples[i as usize].len() as u32);
alpha_tile_patches.push(pp);
}
w16(&mut f, alpha_grid_id);
w16(&mut f, 1);
w16(&mut f, 0);
w16(&mut f, 1);
w32(&mut f, alpha_grid_offset);
w32(&mut f, grid_payload.len() as u32);
if has_exif {
w16(&mut f, exif_id);
w16(&mut f, 0);
w16(&mut f, 0);
w16(&mut f, 1);
exif_patch_pos = f.len();
w32(&mut f, 0);
w32(&mut f, exif_payload.len() as u32);
}
patch(&mut f, s);
}
{
let s = f.len();
write_fullbox(&mut f, b"iinf", 0, 0);
let count = 2 * n + 2 + if has_exif { 1 } else { 0 };
w16(&mut f, count);
for i in 0..n {
let si = f.len();
w32(&mut f, 0);
f.extend_from_slice(b"infe");
f.push(2);
f.push(0);
f.push(0);
f.push(1); w16(&mut f, i + 1);
w16(&mut f, 0);
f.extend_from_slice(b"hvc1");
f.push(0);
patch(&mut f, si);
}
{
let si = f.len();
write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, color_grid_id);
w16(&mut f, 0);
f.extend_from_slice(b"grid");
f.push(0);
patch(&mut f, si);
}
for i in 0..n {
let si = f.len();
w32(&mut f, 0);
f.extend_from_slice(b"infe");
f.push(2);
f.push(0);
f.push(0);
f.push(1);
w16(&mut f, alpha_tile_base + i);
w16(&mut f, 0);
f.extend_from_slice(b"hvc1");
f.push(0);
patch(&mut f, si);
}
{
let si = f.len();
write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, alpha_grid_id);
w16(&mut f, 0);
f.extend_from_slice(b"grid");
f.push(0);
patch(&mut f, si);
}
if has_exif {
let si = f.len();
write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, exif_id);
w16(&mut f, 0);
f.extend_from_slice(b"Exif");
f.push(0);
patch(&mut f, si);
}
patch(&mut f, s);
}
{
let s = f.len();
write_fullbox(&mut f, b"iref", 0, 0);
{
let sr = f.len();
write_box(&mut f, b"dimg");
w16(&mut f, color_grid_id);
w16(&mut f, n);
for i in 1..=n {
w16(&mut f, i);
}
patch(&mut f, sr);
}
{
let sr = f.len();
write_box(&mut f, b"dimg");
w16(&mut f, alpha_grid_id);
w16(&mut f, n);
for i in 0..n {
w16(&mut f, alpha_tile_base + i);
}
patch(&mut f, sr);
}
{
let sr = f.len();
write_box(&mut f, b"auxl");
w16(&mut f, alpha_grid_id);
w16(&mut f, 1);
w16(&mut f, color_grid_id);
patch(&mut f, sr);
}
if has_exif {
let sr = f.len();
write_box(&mut f, b"cdsc");
w16(&mut f, exif_id);
w16(&mut f, 1);
w16(&mut f, color_grid_id);
patch(&mut f, sr);
}
patch(&mut f, s);
}
let mut irot_idx = 0u8;
let mut imir_idx = 0u8;
let mut clli_idx = 0u8;
let mut colr2_idx = 0u8;
{
let s = f.len();
write_box(&mut f, b"iprp");
{
let si = f.len();
write_box(&mut f, b"ipco");
{
let sh = f.len();
write_box(&mut f, b"hvcC");
f.extend_from_slice(&color_hvcc);
patch(&mut f, sh);
}
{
let sh = f.len();
write_fullbox(&mut f, b"ispe", 0, 0);
w32(&mut f, tile_w);
w32(&mut f, tile_h);
patch(&mut f, sh);
}
{
let sh = f.len();
write_fullbox(&mut f, b"ispe", 0, 0);
w32(&mut f, full_w);
w32(&mut f, full_h);
patch(&mut f, sh);
}
write_colr(&mut f, color_meta);
{
let sh = f.len();
write_fullbox(&mut f, b"pixi", 0, 0);
f.push(3);
f.push(bit_depth.bits());
f.push(bit_depth.bits());
f.push(bit_depth.bits());
patch(&mut f, sh);
}
{
let sh = f.len();
write_box(&mut f, b"hvcC");
f.extend_from_slice(&alpha_hvcc);
patch(&mut f, sh);
}
{
let sh = f.len();
write_fullbox(&mut f, b"pixi", 0, 0);
f.push(1);
f.push(bit_depth.bits());
patch(&mut f, sh);
}
{
let sh = f.len();
write_fullbox(&mut f, b"auxC", 0, 0);
f.extend_from_slice(ALPHA_URN);
patch(&mut f, sh);
}
let mut next: u8 = 9;
if has_secondary_colr(color_meta) {
write_secondary_colr(&mut f, color_meta);
colr2_idx = next;
next += 1;
}
if metadata.orientation.irot_steps() != 0 {
let sh = f.len();
write_box(&mut f, b"irot");
f.push(metadata.orientation.irot_steps() & 3);
patch(&mut f, sh);
irot_idx = next;
next += 1;
}
if let Some(ax) = metadata.orientation.imir_axis() {
let sh = f.len();
write_box(&mut f, b"imir");
f.push(if ax { 1 } else { 0 });
patch(&mut f, sh);
imir_idx = next;
next += 1;
}
if let Some(cll) = metadata.content_light_level {
let sh = f.len();
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 si = f.len();
write_fullbox(&mut f, b"ipma", 0, 0);
let entry_count = 2 * n + 2; w32(&mut f, entry_count as u32);
for i in 1..=n {
w16(&mut f, i);
f.push(3);
f.push(0x80 | 1);
f.push(0x80 | 2);
f.push(0x80 | 4);
}
w16(&mut f, color_grid_id);
let mut ga: Vec<u8> = vec![3, 0x80 | 4, 5];
if colr2_idx != 0 {
ga.push(colr2_idx); }
if irot_idx != 0 {
ga.push(0x80 | irot_idx);
}
if imir_idx != 0 {
ga.push(0x80 | imir_idx);
}
if clli_idx != 0 {
ga.push(clli_idx);
}
f.push(ga.len() as u8);
f.extend_from_slice(&ga);
for i in 0..n {
w16(&mut f, alpha_tile_base + i);
f.push(2);
f.push(0x80 | 6);
f.push(0x80 | 2);
}
w16(&mut f, alpha_grid_id);
f.push(3);
f.push(3);
f.push(7);
f.push(8);
patch(&mut f, si);
}
patch(&mut f, s);
}
{
let s = f.len();
write_box(&mut f, b"idat");
f.extend_from_slice(&grid_payload); f.extend_from_slice(&grid_payload); patch(&mut f, s);
}
patch(&mut f, meta_start);
let mdat_start = f.len();
write_box(&mut f, b"mdat");
let mut color_abs: Vec<u32> = Vec::with_capacity(n as usize);
for s in &color_samples {
color_abs.push(f.len() as u32);
f.extend_from_slice(s);
}
let mut alpha_abs: Vec<u32> = Vec::with_capacity(n as usize);
for s in &alpha_samples {
alpha_abs.push(f.len() as u32);
f.extend_from_slice(s);
}
let exif_abs = f.len() as u32;
if has_exif {
f.extend_from_slice(&exif_payload);
}
patch(&mut f, mdat_start);
for (i, &abs) in color_abs.iter().enumerate() {
f[color_tile_patches[i]..color_tile_patches[i] + 4].copy_from_slice(&abs.to_be_bytes());
}
for (i, &abs) in alpha_abs.iter().enumerate() {
f[alpha_tile_patches[i]..alpha_tile_patches[i] + 4].copy_from_slice(&abs.to_be_bytes());
}
if has_exif {
f[exif_patch_pos..exif_patch_pos + 4].copy_from_slice(&exif_abs.to_be_bytes());
}
Ok(f)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hevc::NaluStream;
fn make_stream(chroma: crate::fmt::ChromaFormat, bd: crate::fmt::BitDepth) -> NaluStream {
use crate::hevc::{build_pps, build_sps, build_vps};
NaluStream {
nalus: vec![
build_vps(16, 16, chroma, bd, false),
build_sps(16, 16, chroma, bd, false, Some(&crate::color::Cicp::srgb())),
build_pps(30, false, false),
],
}
}
fn make_test_stream() -> NaluStream {
make_stream(
crate::fmt::ChromaFormat::Yuv420,
crate::fmt::BitDepth::Eight,
)
}
fn make_lossless_stream() -> NaluStream {
use crate::hevc::{build_pps, build_sps, build_vps};
let chroma = crate::fmt::ChromaFormat::Yuv420;
let bit_depth = crate::fmt::BitDepth::Eight;
NaluStream {
nalus: vec![
build_vps(16, 16, chroma, bit_depth, true),
build_sps(
16,
16,
chroma,
bit_depth,
true,
Some(&crate::color::Cicp::srgb()),
),
build_pps(30, true, false),
],
}
}
#[test]
fn ftyp_heic_for_420() {
let b = wrap_hevc_image(
&make_test_stream(),
16,
16,
ImageMeta {
bit_depth: crate::fmt::BitDepth::Eight,
color_meta: &crate::color::ColorMetadata::default(),
metadata: &crate::metadata::Metadata::default(),
},
)
.unwrap();
assert_eq!(&b[4..8], b"ftyp");
assert_eq!(&b[8..12], b"heic", "4:2:0 8-bit must use heic brand");
}
#[test]
fn ftyp_heix_for_lossless_420_rext() {
let b = wrap_hevc_image(
&make_lossless_stream(),
16,
16,
ImageMeta {
bit_depth: crate::fmt::BitDepth::Eight,
color_meta: &crate::color::ColorMetadata::default(),
metadata: &crate::metadata::Metadata::default(),
},
)
.unwrap();
assert_eq!(
&b[8..12],
b"heix",
"implicit-RDPCM lossless streams advertise an RExt profile"
);
}
#[test]
fn ftyp_heix_for_422() {
let b = wrap_hevc_image(
&make_stream(crate::fmt::ChromaFormat::Yuv422, crate::fmt::BitDepth::Ten),
16,
16,
ImageMeta {
bit_depth: crate::fmt::BitDepth::Ten,
color_meta: &crate::color::ColorMetadata::default(),
metadata: &crate::metadata::Metadata::default(),
},
)
.unwrap();
assert_eq!(&b[8..12], b"heix", "4:2:2 must use heix brand");
}
#[test]
fn ftyp_heix_for_444() {
let b = wrap_hevc_image(
&make_stream(
crate::fmt::ChromaFormat::Yuv444,
crate::fmt::BitDepth::Eight,
),
16,
16,
ImageMeta {
bit_depth: crate::fmt::BitDepth::Eight,
color_meta: &crate::color::ColorMetadata::default(),
metadata: &crate::metadata::Metadata::default(),
},
)
.unwrap();
assert_eq!(&b[8..12], b"heix", "4:4:4 must use heix brand");
}
#[test]
fn iloc_version_0() {
let b = wrap_hevc_image(
&make_test_stream(),
16,
16,
ImageMeta {
bit_depth: crate::fmt::BitDepth::Eight,
color_meta: &crate::color::ColorMetadata::default(),
metadata: &crate::metadata::Metadata::default(),
},
)
.unwrap();
let iloc_fourcc = b.array_windows::<4>().position(|w| w == b"iloc").unwrap();
let ver = b[iloc_fourcc + 4]; let base_sz = (b[iloc_fourcc + 9] >> 4) & 0xF; assert_eq!(ver, 0, "iloc must be version 0");
assert_eq!(base_sz, 4, "base_offset_size must be 4");
}
#[test]
fn box_order_ftyp_meta_mdat() {
let b = wrap_hevc_image(
&make_test_stream(),
16,
16,
ImageMeta {
bit_depth: crate::fmt::BitDepth::Eight,
color_meta: &crate::color::ColorMetadata::default(),
metadata: &crate::metadata::Metadata::default(),
},
)
.unwrap();
let ftyp_size = u32::from_be_bytes(b[0..4].try_into().unwrap()) as usize;
assert_eq!(&b[ftyp_size + 4..ftyp_size + 8], b"meta");
let meta_size =
u32::from_be_bytes(b[ftyp_size..ftyp_size + 4].try_into().unwrap()) as usize;
assert_eq!(
&b[ftyp_size + meta_size + 4..ftyp_size + meta_size + 8],
b"mdat"
);
}
#[test]
fn iloc_offset_points_into_mdat() {
let b = wrap_hevc_image(
&make_test_stream(),
16,
16,
ImageMeta {
bit_depth: crate::fmt::BitDepth::Eight,
color_meta: &crate::color::ColorMetadata::default(),
metadata: &crate::metadata::Metadata::default(),
},
)
.unwrap();
let mut pos = 0usize;
let mut mdat_payload = 0u32;
while pos + 8 <= b.len() {
let sz = u32::from_be_bytes(b[pos..pos + 4].try_into().unwrap()) as usize;
if &b[pos + 4..pos + 8] == b"mdat" {
mdat_payload = (pos + 8) as u32;
break;
}
pos += sz;
}
assert!(mdat_payload > 0, "mdat not found");
let iloc_pos = b.array_windows::<4>().position(|w| w == b"iloc").unwrap() - 4;
let offset_pos = iloc_pos + 16 + 10;
let extent_offset = u32::from_be_bytes(b[offset_pos..offset_pos + 4].try_into().unwrap());
assert_eq!(extent_offset, mdat_payload);
}
#[test]
fn ipco_order_hvcc_colr_ispe_pixi() {
let b = wrap_hevc_image(
&make_test_stream(),
16,
16,
ImageMeta {
bit_depth: crate::fmt::BitDepth::Eight,
color_meta: &crate::color::ColorMetadata::default(),
metadata: &crate::metadata::Metadata::default(),
},
)
.unwrap();
let ipco_start = b.array_windows::<4>().position(|w| w == b"ipco").unwrap() + 4;
let first_child = &b[ipco_start + 4..ipco_start + 8];
assert_eq!(first_child, b"hvcC", "first ipco property must be hvcC");
let hvcc_sz =
u32::from_be_bytes(b[ipco_start..ipco_start + 4].try_into().unwrap()) as usize;
let second_child = &b[ipco_start + hvcc_sz + 4..ipco_start + hvcc_sz + 8];
assert_eq!(second_child, b"colr", "second ipco property must be colr");
}
#[test]
fn alpha_container_structure() {
let color = make_test_stream();
let alpha = make_test_stream();
let b = wrap_hevc_image_with_alpha(
&color,
&alpha,
16,
16,
ImageMeta {
bit_depth: crate::fmt::BitDepth::Eight,
color_meta: &crate::color::ColorMetadata::default(),
metadata: &crate::metadata::Metadata::default(),
},
)
.unwrap();
let s = b.as_slice();
assert!(s.array_windows::<4>().any(|w| w == b"iref"));
assert!(s.array_windows::<4>().any(|w| w == b"auxl"));
assert!(s.array_windows::<4>().any(|w| w == b"auxC"));
assert!(
s.array_windows::<26>()
.any(|w| w == b"urn:mpeg:hevc:2015:auxid:1")
);
let ipma_pos = s.array_windows::<4>().position(|w| w == b"ipma").unwrap();
let entry_count = u32::from_be_bytes(s[ipma_pos + 8..ipma_pos + 12].try_into().unwrap());
assert_eq!(entry_count, 2);
}
}