const JXL_CONTAINER_SIGNATURE: [u8; 12] = [
0x00, 0x00, 0x00, 0x0C, b'J', b'X', b'L', b' ', 0x0D, 0x0A, 0x87, 0x0A, ];
const FTYP_BOX: [u8; 20] = [
0x00, 0x00, 0x00, 0x14, b'f', b't', b'y', b'p', b'j', b'x', b'l', b' ', 0x00, 0x00, 0x00, 0x00, b'j', b'x', b'l', b' ', ];
pub fn wrap_in_container(codestream: &[u8], exif: Option<&[u8]>, xmp: Option<&[u8]>) -> Vec<u8> {
wrap_in_container_with_jbrd(codestream, None, exif, xmp)
}
pub fn wrap_in_container_with_jbrd(
codestream: &[u8],
jbrd: Option<&[u8]>,
exif: Option<&[u8]>,
xmp: Option<&[u8]>,
) -> Vec<u8> {
let header_size = JXL_CONTAINER_SIGNATURE.len() + FTYP_BOX.len(); let jxlc_size = 8 + codestream.len();
let jbrd_size = jbrd.map_or(0, |j| 8 + j.len());
let exif_size = exif.map_or(0, |e| 8 + 4 + e.len()); let xmp_size = xmp.map_or(0, |x| 8 + x.len());
let total = header_size + jxlc_size + jbrd_size + exif_size + xmp_size;
let mut out = Vec::with_capacity(total);
out.extend_from_slice(&JXL_CONTAINER_SIGNATURE);
out.extend_from_slice(&FTYP_BOX);
write_box(&mut out, b"jxlc", codestream);
if let Some(jbrd_data) = jbrd {
write_box(&mut out, b"jbrd", jbrd_data);
}
if let Some(exif_data) = exif {
write_exif_box(&mut out, exif_data);
}
if let Some(xmp_data) = xmp {
write_box(&mut out, b"xml ", xmp_data);
}
out
}
#[cfg(feature = "jpeg-reencoding")]
pub fn wrap_in_container_jxlp(
cs_part1: &[u8],
cs_part2: &[u8],
jbrd: &[u8],
exif: Option<&[u8]>,
xmp: Option<&[u8]>,
) -> Vec<u8> {
let header_size = JXL_CONTAINER_SIGNATURE.len() + FTYP_BOX.len(); let jxlp1_size = 8 + 4 + cs_part1.len();
let jxlp2_size = 8 + 4 + cs_part2.len();
let jbrd_size = 8 + jbrd.len();
let exif_size = exif.map_or(0, |e| 8 + 4 + e.len());
let xmp_size = xmp.map_or(0, |x| 8 + x.len());
let total = header_size + jxlp1_size + jbrd_size + jxlp2_size + exif_size + xmp_size;
let mut out = Vec::with_capacity(total);
out.extend_from_slice(&JXL_CONTAINER_SIGNATURE);
out.extend_from_slice(&FTYP_BOX);
write_jxlp_box(&mut out, 0, false, cs_part1);
write_box(&mut out, b"jbrd", jbrd);
write_jxlp_box(&mut out, 1, true, cs_part2);
if let Some(exif_data) = exif {
write_exif_box(&mut out, exif_data);
}
if let Some(xmp_data) = xmp {
write_box(&mut out, b"xml ", xmp_data);
}
out
}
#[allow(dead_code)]
fn write_jxlp_box(out: &mut Vec<u8>, sequence: u32, is_last: bool, data: &[u8]) {
let total_size = 8u64 + 4 + data.len() as u64;
if total_size <= u32::MAX as u64 {
out.extend_from_slice(&(total_size as u32).to_be_bytes());
out.extend_from_slice(b"jxlp");
} else {
let extended_size = 16u64 + 4 + data.len() as u64;
out.extend_from_slice(&1u32.to_be_bytes());
out.extend_from_slice(b"jxlp");
out.extend_from_slice(&extended_size.to_be_bytes());
}
let counter = if is_last {
sequence | 0x8000_0000
} else {
sequence
};
out.extend_from_slice(&counter.to_be_bytes());
out.extend_from_slice(data);
}
fn write_exif_box(out: &mut Vec<u8>, exif_data: &[u8]) {
let payload_size = 4u64 + exif_data.len() as u64;
let total_size = 8u64 + payload_size;
if total_size <= u32::MAX as u64 {
out.extend_from_slice(&(total_size as u32).to_be_bytes());
out.extend_from_slice(b"Exif");
} else {
let extended_size = 16u64 + payload_size;
out.extend_from_slice(&1u32.to_be_bytes());
out.extend_from_slice(b"Exif");
out.extend_from_slice(&extended_size.to_be_bytes());
}
out.extend_from_slice(&[0u8; 4]); out.extend_from_slice(exif_data);
}
fn write_box(out: &mut Vec<u8>, box_type: &[u8; 4], payload: &[u8]) {
let total_size = 8u64 + payload.len() as u64;
if total_size <= u32::MAX as u64 {
out.extend_from_slice(&(total_size as u32).to_be_bytes());
out.extend_from_slice(box_type);
} else {
let extended_size = 16u64 + payload.len() as u64;
out.extend_from_slice(&1u32.to_be_bytes()); out.extend_from_slice(box_type);
out.extend_from_slice(&extended_size.to_be_bytes());
}
out.extend_from_slice(payload);
}
#[must_use]
pub fn is_container(data: &[u8]) -> bool {
data.len() >= 12 && data[..12] == JXL_CONTAINER_SIGNATURE
}
#[must_use]
pub fn is_bare_codestream(data: &[u8]) -> bool {
data.len() >= 2 && data[0] == 0xFF && data[1] == 0x0A
}
#[must_use]
pub fn append_gain_map_box(jxl_data: &[u8], jhgm_payload: &[u8]) -> Vec<u8> {
if is_container(jxl_data) {
let jhgm_box_size = 8 + jhgm_payload.len();
let mut out = Vec::with_capacity(jxl_data.len() + jhgm_box_size);
out.extend_from_slice(jxl_data);
write_box(&mut out, b"jhgm", jhgm_payload);
out
} else {
let header_size = JXL_CONTAINER_SIGNATURE.len() + FTYP_BOX.len();
let jxlc_size = 8 + jxl_data.len();
let jhgm_box_size = 8 + jhgm_payload.len();
let total = header_size + jxlc_size + jhgm_box_size;
let mut out = Vec::with_capacity(total);
out.extend_from_slice(&JXL_CONTAINER_SIGNATURE);
out.extend_from_slice(&FTYP_BOX);
write_box(&mut out, b"jxlc", jxl_data);
write_box(&mut out, b"jhgm", jhgm_payload);
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_container_no_metadata() {
let codestream = b"\xFF\x0A\x00\x00"; let result = wrap_in_container(codestream, None, None);
assert_eq!(result.len(), 32 + 8 + 4);
assert_eq!(&result[..4], &[0, 0, 0, 0x0C]);
assert_eq!(&result[4..8], b"JXL ");
}
#[test]
fn test_container_with_exif() {
let codestream = b"\xFF\x0A";
let exif = b"Exif\x00\x00MM\x00\x2a"; let result = wrap_in_container(codestream, Some(exif), None);
assert_eq!(result.len(), 32 + 10 + 22);
let exif_start = 32 + 10; assert_eq!(&result[exif_start + 4..exif_start + 8], b"Exif");
assert_eq!(&result[exif_start + 8..exif_start + 12], &[0, 0, 0, 0]);
}
#[test]
fn test_container_with_xmp() {
let codestream = b"\xFF\x0A";
let xmp = b"<?xpacket begin='...'?>";
let result = wrap_in_container(codestream, None, Some(xmp));
assert_eq!(result.len(), 32 + 10 + 31);
let xml_start = 32 + 10;
assert_eq!(&result[xml_start + 4..xml_start + 8], b"xml ");
}
#[test]
fn test_container_with_both() {
let codestream = b"\xFF\x0A";
let exif = b"EX";
let xmp = b"XM";
let result = wrap_in_container(codestream, Some(exif), Some(xmp));
assert_eq!(result.len(), 32 + 10 + 14 + 10);
}
#[test]
fn test_is_container() {
assert!(is_container(&JXL_CONTAINER_SIGNATURE));
assert!(!is_container(&[0xFF, 0x0A, 0x00]));
assert!(!is_container(&[0x00; 4]));
}
#[test]
fn test_is_bare_codestream() {
assert!(is_bare_codestream(&[0xFF, 0x0A]));
assert!(is_bare_codestream(&[0xFF, 0x0A, 0x00, 0x01]));
assert!(!is_bare_codestream(&[0xFF, 0x0B]));
assert!(!is_bare_codestream(&[0x00]));
}
#[test]
fn test_append_gain_map_to_bare_codestream() {
let codestream = b"\xFF\x0A\x00\x01\x02\x03";
let jhgm_payload = b"\x00\x00\x03\x01\x02\x03\x00\x00\x00\x00\x00\xFF\x0A";
let result = append_gain_map_box(codestream, jhgm_payload);
assert!(is_container(&result));
assert_eq!(&result[..12], &JXL_CONTAINER_SIGNATURE);
assert_eq!(&result[12..32], &FTYP_BOX);
let jxlc_size = u32::from_be_bytes([result[32], result[33], result[34], result[35]]);
assert_eq!(jxlc_size as usize, 8 + codestream.len());
assert_eq!(&result[36..40], b"jxlc");
assert_eq!(&result[40..40 + codestream.len()], codestream);
let jhgm_offset = 40 + codestream.len();
let jhgm_size = u32::from_be_bytes([
result[jhgm_offset],
result[jhgm_offset + 1],
result[jhgm_offset + 2],
result[jhgm_offset + 3],
]);
assert_eq!(jhgm_size as usize, 8 + jhgm_payload.len());
assert_eq!(&result[jhgm_offset + 4..jhgm_offset + 8], b"jhgm");
assert_eq!(&result[jhgm_offset + 8..], jhgm_payload);
}
#[test]
fn test_append_gain_map_to_existing_container() {
let codestream = b"\xFF\x0A\x00";
let mut container = Vec::new();
container.extend_from_slice(&JXL_CONTAINER_SIGNATURE);
container.extend_from_slice(&FTYP_BOX);
write_box(&mut container, b"jxlc", codestream);
let jhgm_payload = b"\x00\x00\x01\xAA\x00\x00\x00\x00\x00\xFF\x0A";
let result = append_gain_map_box(&container, jhgm_payload);
assert_eq!(&result[..container.len()], container.as_slice());
let jhgm_offset = container.len();
assert_eq!(&result[jhgm_offset + 4..jhgm_offset + 8], b"jhgm");
assert_eq!(&result[jhgm_offset + 8..], jhgm_payload);
}
#[test]
fn test_write_box_small() {
let mut out = Vec::new();
write_box(&mut out, b"test", b"hello");
assert_eq!(out.len(), 8 + 5);
let size = u32::from_be_bytes([out[0], out[1], out[2], out[3]]);
assert_eq!(size, 13);
assert_eq!(&out[4..8], b"test");
assert_eq!(&out[8..], b"hello");
}
#[test]
fn test_write_box_empty_payload() {
let mut out = Vec::new();
write_box(&mut out, b"emty", b"");
assert_eq!(out.len(), 8);
let size = u32::from_be_bytes([out[0], out[1], out[2], out[3]]);
assert_eq!(size, 8);
assert_eq!(&out[4..8], b"emty");
}
}