use crate::error::{Jp2Error, Result};
use crate::j2k;
use crate::jp2_box::*;
use crate::stream::{SliceReader, VecWriter};
use crate::tcd::{TcdComponent, TcdParams};
pub fn jp2_encode(
components: &[Vec<i32>],
comp_info: &[TcdComponent],
params: &TcdParams,
) -> Result<Vec<u8>> {
let num_comps = comp_info.len();
if components.len() != num_comps || num_comps == 0 {
return Err(Jp2Error::InvalidData(
"component count mismatch or zero components".to_string(),
));
}
let width = comp_info[0].width;
let height = comp_info[0].height;
let j2k_data = j2k::j2k_encode(components, comp_info, params)?;
let mut writer = VecWriter::new();
write_box_header(&mut writer, JP2_JP, 4);
writer.write_u32_be(JP2_SIGNATURE);
write_box_header(&mut writer, JP2_FTYP, 12);
writer.write_u32_be(JP2_BRAND); writer.write_u32_be(0); writer.write_u32_be(JP2_BRAND);
let bpc = if comp_info[0].signed {
0x80 | ((comp_info[0].precision - 1) as u8)
} else {
(comp_info[0].precision - 1) as u8
};
let ihdr = IhdrBox {
height,
width,
num_comps: num_comps as u16,
bpc,
compression: 7,
unk_colorspace: 0,
ipr: 0,
};
let colr = if num_comps >= 3 {
ColrBox {
method: 1,
precedence: 0,
approx: 0,
enum_cs: Some(CS_SRGB),
icc_profile: None,
}
} else {
ColrBox {
method: 1,
precedence: 0,
approx: 0,
enum_cs: Some(CS_GRAYSCALE),
icc_profile: None,
}
};
let ihdr_box_len = 8 + 14;
let colr_payload_len: u64 = if colr.method == 1 { 7 } else { 3 };
let colr_box_len = 8 + colr_payload_len;
let jp2h_content_len = ihdr_box_len + colr_box_len;
write_box_header(&mut writer, JP2_JP2H, jp2h_content_len);
write_box_header(&mut writer, JP2_IHDR, 14);
write_ihdr(&mut writer, &ihdr);
write_box_header(&mut writer, JP2_COLR, colr_payload_len);
write_colr(&mut writer, &colr);
write_box_header(&mut writer, JP2_JP2C, j2k_data.len() as u64);
writer.write_bytes(&j2k_data);
Ok(writer.into_vec())
}
pub fn jp2_decode(data: &[u8]) -> Result<(Vec<Vec<i32>>, Vec<TcdComponent>)> {
let mut reader = SliceReader::new(data);
let jp_header = read_box_header(&mut reader)?;
if jp_header.box_type != JP2_JP {
return Err(Jp2Error::InvalidData(format!(
"expected JP signature box (0x{:08X}), got 0x{:08X}",
JP2_JP, jp_header.box_type
)));
}
let sig = reader.read_u32_be()?;
if sig != JP2_SIGNATURE {
return Err(Jp2Error::InvalidData(format!(
"invalid JP2 signature: 0x{sig:08X}"
)));
}
let ftyp_header = read_box_header(&mut reader)?;
if ftyp_header.box_type != JP2_FTYP {
return Err(Jp2Error::InvalidData(format!(
"expected FTYP box (0x{:08X}), got 0x{:08X}",
JP2_FTYP, ftyp_header.box_type
)));
}
let ftyp_content_len = ftyp_header.length as usize - ftyp_header.header_size as usize;
reader.skip(ftyp_content_len)?;
let mut _ihdr: Option<IhdrBox> = None;
let mut _colr: Option<ColrBox> = None;
let mut j2k_codestream: Option<&[u8]> = None;
while reader.remaining() >= 8 {
let header = read_box_header(&mut reader)?;
let content_len = if header.length == 0 {
reader.remaining()
} else {
header.length as usize - header.header_size as usize
};
match header.box_type {
JP2_JP2H => {
let jp2h_end = reader.tell() + content_len;
while reader.tell() < jp2h_end && reader.remaining() >= 8 {
let sub_header = read_box_header(&mut reader)?;
let sub_content_len =
sub_header.length as usize - sub_header.header_size as usize;
match sub_header.box_type {
JP2_IHDR => {
_ihdr = Some(read_ihdr(&mut reader)?);
}
JP2_COLR => {
_colr = Some(read_colr(&mut reader, sub_content_len)?);
}
_ => {
reader.skip(sub_content_len)?;
}
}
}
}
JP2_JP2C => {
j2k_codestream = Some(reader.read_bytes(content_len)?);
}
_ => {
reader.skip(content_len)?;
}
}
if j2k_codestream.is_some() {
break;
}
}
let codestream = j2k_codestream
.ok_or_else(|| Jp2Error::InvalidData("no JP2C (codestream) box found".to_string()))?;
j2k::j2k_decode(codestream)
}