use crate::Cicp;
use crate::av2::Av2Frame;
use crate::metadata::{ContentLightLevel, Orientation};
pub(crate) enum Av2Color {
Cicp(Cicp),
#[allow(unused)]
Icc(Vec<u8>),
Both {
cicp: Cicp,
icc: Vec<u8>,
},
}
fn w16(buf: &mut Vec<u8>, v: u16) {
buf.extend_from_slice(&v.to_be_bytes());
}
fn w32(buf: &mut Vec<u8>, v: u32) {
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 = buf.len();
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);
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());
}
#[derive(Clone, Copy)]
pub struct Av2Format {
pub bit_depth: u8,
pub monochrome: bool,
pub chroma_sub_x: bool,
pub chroma_sub_y: bool,
}
impl Av2Format {
fn channels(&self) -> u8 {
if self.monochrome { 1 } else { 3 }
}
fn seq_profile(&self) -> u8 {
if self.monochrome || (self.chroma_sub_x && self.chroma_sub_y) {
0
} else if self.chroma_sub_x {
3
} else {
4
}
}
}
fn level_for(width: u32, height: u32) -> u8 {
let pels = width as u64 * height as u64;
if pels <= 147_456 {
0 } else if pels <= 278_784 {
1 } else if pels <= 665_856 {
4 } else if pels <= 1_065_024 {
5 } else if pels <= 2_359_296 {
8 } else if pels <= 8_912_896 {
12 } else {
16 }
}
fn build_av2c(fmt: &Av2Format, width: u32, height: u32) -> [u8; 4] {
let high_bitdepth = fmt.bit_depth > 8;
let twelve_bit = fmt.bit_depth == 12;
let b0 = 0x81u8;
let b1 = ((fmt.seq_profile() & 0x7) << 5) | (level_for(width, height) & 0x1f);
let b2 = (if high_bitdepth { 0x40 } else { 0 })
| (if twelve_bit { 0x20 } else { 0 })
| (if fmt.monochrome { 0x10 } else { 0 })
| (if fmt.chroma_sub_x { 0x08 } else { 0 })
| (if fmt.chroma_sub_y { 0x04 } else { 0 });
[b0, b1, b2, 0x00]
}
#[derive(Clone, Copy)]
pub(crate) struct AlphaItem<'a> {
pub(crate) obu: &'a [u8],
pub(crate) coded_width: u32,
pub(crate) coded_height: u32,
pub(crate) disp_width: u32,
pub(crate) disp_height: u32,
pub(crate) bit_depth: u8,
}
#[derive(Clone, Copy)]
pub(crate) struct Av2ImageSpec<'a> {
coded_width: u32,
coded_height: u32,
disp_width: u32,
disp_height: u32,
format: &'a Av2Format,
color: &'a Av2Color,
exif: Option<&'a [u8]>,
orientation: Orientation,
clli: Option<ContentLightLevel>,
alpha: Option<AlphaItem<'a>>,
}
pub(crate) fn wrap_av2_image(obu: &[u8], spec: &Av2ImageSpec<'_>) -> Vec<u8> {
let Av2ImageSpec {
coded_width: width,
coded_height: height,
disp_width,
disp_height,
format: fmt,
color,
exif,
orientation,
clli,
alpha,
} = *spec;
let channels = fmt.channels();
let av2c = build_av2c(fmt, width, height);
let has_alpha = alpha.is_some();
let alpha_id: u16 = 2;
let exif_id: u16 = if has_alpha { 3 } else { 2 };
let alpha_av2c = alpha.as_ref().map(|a| {
let af = Av2Format {
bit_depth: a.bit_depth,
monochrome: true,
chroma_sub_x: false,
chroma_sub_y: false,
};
build_av2c(&af, a.coded_width, a.coded_height)
});
let has_exif = exif.is_some();
let exif_block: Vec<u8> = exif
.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::with_capacity(obu.len() + exif_block.len() + 512);
{
let s = write_box(&mut f, b"ftyp");
f.extend_from_slice(b"avif"); w32(&mut f, 0); for b in [b"avif", b"mif1", b"miaf", b"av2f"] {
f.extend_from_slice(b);
}
patch(&mut f, s);
}
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 iloc_offset_pos;
let mut iloc_alpha_pos = 0usize;
let mut iloc_exif_pos = 0usize;
{
let s = write_fullbox(&mut f, b"iloc", 0, 0);
f.push(0x44); f.push(0x00); let item_count = 1 + has_alpha as u16 + has_exif as u16;
w16(&mut f, item_count);
w16(&mut f, 1); w16(&mut f, 0); w16(&mut f, 1); iloc_offset_pos = f.len();
w32(&mut f, 0); w32(&mut f, obu.len() as u32); if let Some(a) = alpha.as_ref() {
w16(&mut f, alpha_id);
w16(&mut f, 0);
w16(&mut f, 1);
iloc_alpha_pos = f.len();
w32(&mut f, 0); w32(&mut f, a.obu.len() as u32);
}
if has_exif {
w16(&mut f, exif_id);
w16(&mut f, 0);
w16(&mut f, 1);
iloc_exif_pos = f.len();
w32(&mut f, 0); w32(&mut f, exif_block.len() as u32);
}
patch(&mut f, s);
}
{
let s = write_fullbox(&mut f, b"iinf", 0, 0);
let entry_count = 1 + has_alpha as u16 + has_exif as u16;
w16(&mut f, entry_count);
{
let si = write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, 1); w16(&mut f, 0); f.extend_from_slice(b"av02"); f.push(0); patch(&mut f, si);
}
if has_alpha {
let si = write_fullbox(&mut f, b"infe", 2, 0);
w16(&mut f, alpha_id); w16(&mut f, 0);
f.extend_from_slice(b"av02"); f.push(0);
patch(&mut f, si);
}
if has_exif {
let si = 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);
}
if has_alpha || has_exif {
let s = write_fullbox(&mut f, b"iref", 0, 0);
if has_alpha {
let si = write_box(&mut f, b"auxl");
w16(&mut f, alpha_id); w16(&mut f, 1); w16(&mut f, 1); patch(&mut f, si);
}
if has_exif {
let si = write_box(&mut f, b"cdsc");
w16(&mut f, exif_id); 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 ipco = write_box(&mut f, b"ipco");
{
let p = write_fullbox(&mut f, b"ispe", 0, 0);
w32(&mut f, width);
w32(&mut f, height);
patch(&mut f, p);
}
{
let p = write_fullbox(&mut f, b"pixi", 0, 0);
f.push(channels);
for _ in 0..channels {
f.push(fmt.bit_depth);
}
patch(&mut f, p);
}
{
let p = write_box(&mut f, b"av2C");
f.extend_from_slice(&av2c);
patch(&mut f, p);
}
let mut colr_props: Vec<u8> = Vec::new();
let mut next_prop: u8 = 4; let write_nclx = |f: &mut Vec<u8>, c: &Cicp| {
let p = write_box(f, b"colr");
f.extend_from_slice(b"nclx");
w16(f, c.primaries as u16);
w16(f, c.transfer as u16);
w16(f, c.matrix as u16);
f.push(if c.full_range { 0x80 } else { 0x00 });
patch(f, p);
};
let write_prof = |f: &mut Vec<u8>, icc: &[u8]| {
let p = write_box(f, b"colr");
f.extend_from_slice(b"prof");
f.extend_from_slice(icc);
patch(f, p);
};
match color {
Av2Color::Cicp(c) => {
write_nclx(&mut f, c);
colr_props.push(next_prop);
next_prop += 1;
}
Av2Color::Icc(icc) => {
write_prof(&mut f, icc);
colr_props.push(next_prop);
next_prop += 1;
}
Av2Color::Both { cicp, icc } => {
write_nclx(&mut f, cicp);
colr_props.push(next_prop);
next_prop += 1;
write_prof(&mut f, icc);
colr_props.push(next_prop);
next_prop += 1;
}
}
let _ = next_prop;
let mut clap_prop: Option<u8> = None;
if disp_width != width || disp_height != height {
let p = write_box(&mut f, b"clap");
w32(&mut f, disp_width); w32(&mut f, 1); w32(&mut f, disp_height); w32(&mut f, 1); w32(&mut f, (disp_width as i32 - width as i32) as u32); w32(&mut f, 2); w32(&mut f, (disp_height as i32 - height as i32) as u32); w32(&mut f, 2); patch(&mut f, p);
clap_prop = Some(next_prop);
next_prop += 1;
}
let mut irot_prop: Option<u8> = None;
let mut imir_prop: Option<u8> = None;
let mut clli_prop: Option<u8> = None;
if orientation.irot_steps() != 0 {
let p = write_box(&mut f, b"irot");
f.push(orientation.irot_steps() & 0x03);
patch(&mut f, p);
irot_prop = Some(next_prop);
next_prop += 1;
}
if let Some(horizontal_axis) = orientation.imir_axis() {
let p = write_box(&mut f, b"imir");
f.push(if horizontal_axis { 1 } else { 0 });
patch(&mut f, p);
imir_prop = Some(next_prop);
next_prop += 1;
}
if let Some(cll) = clli {
let p = write_box(&mut f, b"clli");
f.extend_from_slice(&cll.clli_payload());
patch(&mut f, p);
clli_prop = Some(next_prop);
next_prop += 1;
}
let mut alpha_props: Option<(u8, u8, u8, u8, Option<u8>)> = None;
if let Some(a) = alpha.as_ref() {
let ispe_a = next_prop;
next_prop += 1;
{
let p = write_fullbox(&mut f, b"ispe", 0, 0);
w32(&mut f, a.coded_width);
w32(&mut f, a.coded_height);
patch(&mut f, p);
}
let pixi_a = next_prop;
next_prop += 1;
{
let p = write_fullbox(&mut f, b"pixi", 0, 0);
f.push(1); f.push(a.bit_depth);
patch(&mut f, p);
}
let av2c_a = next_prop;
next_prop += 1;
{
let p = write_box(&mut f, b"av2C");
f.extend_from_slice(alpha_av2c.as_ref().unwrap());
patch(&mut f, p);
}
let auxc_a = next_prop;
next_prop += 1;
{
let p = write_fullbox(&mut f, b"auxC", 0, 0);
f.extend_from_slice(b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha");
f.push(0); patch(&mut f, p);
}
let mut clap_a = None;
if a.disp_width != a.coded_width || a.disp_height != a.coded_height {
let p = write_box(&mut f, b"clap");
w32(&mut f, a.disp_width);
w32(&mut f, 1);
w32(&mut f, a.disp_height);
w32(&mut f, 1);
w32(&mut f, (a.disp_width as i32 - a.coded_width as i32) as u32);
w32(&mut f, 2);
w32(
&mut f,
(a.disp_height as i32 - a.coded_height as i32) as u32,
);
w32(&mut f, 2);
patch(&mut f, p);
clap_a = Some(next_prop);
next_prop += 1;
}
alpha_props = Some((ispe_a, pixi_a, av2c_a, auxc_a, clap_a));
}
let _ = next_prop;
patch(&mut f, ipco);
{
let p = write_fullbox(&mut f, b"ipma", 0, 0);
w32(&mut f, 1 + has_alpha as u32); w16(&mut f, 1);
let assoc = 3
+ colr_props.len() as u8
+ clap_prop.is_some() as u8
+ irot_prop.is_some() as u8
+ imir_prop.is_some() as u8
+ clli_prop.is_some() as u8;
f.push(assoc);
f.push(1); f.push(2); f.push(0x80 | 3); for &idx in &colr_props {
f.push(idx); }
if let Some(idx) = clap_prop {
f.push(0x80 | idx); }
if let Some(idx) = irot_prop {
f.push(0x80 | idx); }
if let Some(idx) = imir_prop {
f.push(0x80 | idx); }
if let Some(idx) = clli_prop {
f.push(idx); }
if let Some((ispe_a, pixi_a, av2c_a, auxc_a, clap_a)) = alpha_props {
w16(&mut f, alpha_id);
f.push(4 + clap_a.is_some() as u8);
f.push(ispe_a);
f.push(pixi_a);
f.push(0x80 | av2c_a); f.push(0x80 | auxc_a); if let Some(idx) = clap_a {
f.push(0x80 | idx);
}
}
patch(&mut f, p);
}
patch(&mut f, s);
}
patch(&mut f, meta_start);
let mdat_start = write_box(&mut f, b"mdat");
let payload_off = f.len();
f.extend_from_slice(obu);
let alpha_off = f.len();
if let Some(a) = alpha.as_ref() {
f.extend_from_slice(a.obu);
}
let exif_off = f.len();
if has_exif {
f.extend_from_slice(&exif_block);
}
patch(&mut f, mdat_start);
f[iloc_offset_pos..iloc_offset_pos + 4].copy_from_slice(&(payload_off as u32).to_be_bytes());
if has_alpha {
f[iloc_alpha_pos..iloc_alpha_pos + 4].copy_from_slice(&(alpha_off as u32).to_be_bytes());
}
if has_exif {
f[iloc_exif_pos..iloc_exif_pos + 4].copy_from_slice(&(exif_off as u32).to_be_bytes());
}
f
}
pub(crate) fn to_avif_color(
enc: &Av2Frame,
fmt: &Av2Format,
color: &Av2Color,
exif: Option<&[u8]>,
orientation: Orientation,
clli: Option<ContentLightLevel>,
) -> Vec<u8> {
wrap_av2_image(
&enc.data,
&Av2ImageSpec {
coded_width: enc.coded_width as u32,
coded_height: enc.coded_height as u32,
disp_width: enc.width as u32,
disp_height: enc.height as u32,
format: fmt,
color,
exif,
orientation,
clli,
alpha: None,
},
)
}
pub(crate) fn to_avif_color_alpha(
enc: &Av2Frame,
alpha: &Av2Frame,
fmt: &Av2Format,
color: &Av2Color,
exif: Option<&[u8]>,
orientation: Orientation,
clli: Option<ContentLightLevel>,
) -> Vec<u8> {
wrap_av2_image(
&enc.data,
&Av2ImageSpec {
coded_width: enc.coded_width as u32,
coded_height: enc.coded_height as u32,
disp_width: enc.width as u32,
disp_height: enc.height as u32,
format: fmt,
color,
exif,
orientation,
clli,
alpha: Some(AlphaItem {
obu: &alpha.data,
coded_width: alpha.coded_width as u32,
coded_height: alpha.coded_height as u32,
disp_width: alpha.width as u32,
disp_height: alpha.height as u32,
bit_depth: alpha.bit_depth,
}),
},
)
}