pub mod bio;
pub mod dwt;
pub mod error;
pub mod htj2k;
pub mod j2k;
pub mod jp2;
pub mod jp2_box;
pub mod marker;
pub mod mct;
pub mod mqc;
pub mod pi;
pub mod quantize;
pub mod simd;
pub mod stream;
pub mod t1;
pub mod t2;
pub mod tgt;
pub mod tcd;
pub mod types;
pub use error::{Jp2Error, Result};
pub use types::CodecFormat;
use tcd::{TcdComponent, TcdParams};
#[derive(Debug, Clone)]
pub struct Image {
pub width: u32,
pub height: u32,
pub components: Vec<Component>,
}
#[derive(Debug, Clone)]
pub struct Component {
pub data: Vec<i32>,
pub width: u32,
pub height: u32,
pub precision: u32,
pub signed: bool,
pub dx: u32,
pub dy: u32,
}
#[derive(Debug, Clone)]
pub struct EncodeParams {
pub lossless: bool,
pub num_decomp_levels: u32,
pub cblk_width: u32,
pub cblk_height: u32,
pub format: CodecFormat,
}
impl Default for EncodeParams {
fn default() -> Self {
Self {
lossless: true,
num_decomp_levels: 5,
cblk_width: 64,
cblk_height: 64,
format: CodecFormat::Jp2,
}
}
}
pub fn decode(data: &[u8]) -> Result<Image> {
decode_with_reduce(data, 0)
}
pub fn decode_with_reduce(data: &[u8], reduce: u32) -> Result<Image> {
if data.len() < 4 {
return Err(Jp2Error::InvalidData(
"data too short to detect format".to_string(),
));
}
let format = detect_format(data)?;
let (components_data, comp_info) = match format {
CodecFormat::Jp2 => {
if reduce > 0 {
jp2::jp2_decode(data)?
} else {
jp2::jp2_decode(data)?
}
}
CodecFormat::J2k => j2k::j2k_decode_with_reduce(data, reduce)?,
};
let width = comp_info[0].width;
let height = comp_info[0].height;
let components = components_data
.into_iter()
.zip(comp_info.iter())
.map(|(data, ci)| Component {
data,
width: ci.width,
height: ci.height,
precision: ci.precision,
signed: ci.signed,
dx: ci.dx,
dy: ci.dy,
})
.collect();
Ok(Image {
width,
height,
components,
})
}
pub fn decode_region(data: &[u8], x0: u32, y0: u32, x1: u32, y1: u32) -> Result<Image> {
if x0 >= x1 || y0 >= y1 {
return Err(Jp2Error::InvalidData(
"decode_region: empty region (x0 >= x1 or y0 >= y1)".to_string(),
));
}
let full = decode(data)?;
if x1 > full.width || y1 > full.height {
return Err(Jp2Error::InvalidData(format!(
"decode_region: region ({x0},{y0})-({x1},{y1}) exceeds image bounds {}x{}",
full.width, full.height
)));
}
let region_w = x1 - x0;
let region_h = y1 - y0;
let components = full
.components
.iter()
.map(|comp| {
let cx0 = x0 / comp.dx;
let cy0 = y0 / comp.dy;
let cx1 = ((x1 + comp.dx - 1) / comp.dx).min(comp.width);
let cy1 = ((y1 + comp.dy - 1) / comp.dy).min(comp.height);
let cw = cx1 - cx0;
let ch = cy1 - cy0;
let mut region_data = vec![0i32; (cw * ch) as usize];
for y in 0..ch {
for x in 0..cw {
let src_idx = ((cy0 + y) * comp.width + (cx0 + x)) as usize;
let dst_idx = (y * cw + x) as usize;
region_data[dst_idx] = comp.data[src_idx];
}
}
Component {
data: region_data,
width: cw,
height: ch,
precision: comp.precision,
signed: comp.signed,
dx: comp.dx,
dy: comp.dy,
}
})
.collect();
Ok(Image {
width: region_w,
height: region_h,
components,
})
}
pub fn encode(image: &Image, params: &EncodeParams) -> Result<Vec<u8>> {
if image.components.is_empty() {
return Err(Jp2Error::InvalidData(
"image has no components".to_string(),
));
}
let comp_info: Vec<TcdComponent> = image
.components
.iter()
.map(|c| TcdComponent {
width: c.width,
height: c.height,
precision: c.precision,
signed: c.signed,
dx: c.dx,
dy: c.dy,
})
.collect();
let components_data: Vec<Vec<i32>> = image
.components
.iter()
.map(|c| c.data.clone())
.collect();
let use_mct = image.components.len() >= 3;
let tcd_params = TcdParams {
num_res: params.num_decomp_levels + 1,
cblk_w: params.cblk_width,
cblk_h: params.cblk_height,
reversible: params.lossless,
num_layers: 1,
use_mct,
reduce: 0,
max_bytes: None,
};
match params.format {
CodecFormat::Jp2 => jp2::jp2_encode(&components_data, &comp_info, &tcd_params),
CodecFormat::J2k => j2k::j2k_encode(&components_data, &comp_info, &tcd_params),
}
}
fn detect_format(data: &[u8]) -> Result<CodecFormat> {
if data.len() < 4 {
return Err(Jp2Error::InvalidData(
"data too short to detect format".to_string(),
));
}
if data.len() >= 12 {
let box_type = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
if box_type == jp2_box::JP2_JP {
return Ok(CodecFormat::Jp2);
}
}
if data[0] == 0xFF && data[1] == 0x4F {
return Ok(CodecFormat::J2k);
}
Err(Jp2Error::InvalidData(
"unrecognized format: not JP2 or J2K".to_string(),
))
}