use crate::error::{Jpeg2000Error, Result};
use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Read, Seek, SeekFrom};
pub const JP2_SIGNATURE: [u8; 12] = [
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A, ];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoxType {
Signature,
FileType,
Jp2Header,
ImageHeader,
BitsPerComponent,
ColorSpecification,
Palette,
ComponentMapping,
ChannelDefinition,
Resolution,
CaptureResolution,
DisplayResolution,
ContiguousCodestream,
Xml,
Uuid,
UuidInfo,
UuidList,
Url,
IntellectualProperty,
Unknown([u8; 4]),
}
impl BoxType {
pub fn from_bytes(bytes: &[u8; 4]) -> Self {
match bytes {
b"jP " => Self::Signature,
b"ftyp" => Self::FileType,
b"jp2h" => Self::Jp2Header,
b"ihdr" => Self::ImageHeader,
b"bpcc" => Self::BitsPerComponent,
b"colr" => Self::ColorSpecification,
b"pclr" => Self::Palette,
b"cmap" => Self::ComponentMapping,
b"cdef" => Self::ChannelDefinition,
b"res " => Self::Resolution,
b"resc" => Self::CaptureResolution,
b"resd" => Self::DisplayResolution,
b"jp2c" => Self::ContiguousCodestream,
b"xml " => Self::Xml,
b"uuid" => Self::Uuid,
b"uinf" => Self::UuidInfo,
b"ulst" => Self::UuidList,
b"url " => Self::Url,
b"iprp" => Self::IntellectualProperty,
_ => Self::Unknown(*bytes),
}
}
pub fn to_bytes(&self) -> [u8; 4] {
match self {
Self::Signature => *b"jP ",
Self::FileType => *b"ftyp",
Self::Jp2Header => *b"jp2h",
Self::ImageHeader => *b"ihdr",
Self::BitsPerComponent => *b"bpcc",
Self::ColorSpecification => *b"colr",
Self::Palette => *b"pclr",
Self::ComponentMapping => *b"cmap",
Self::ChannelDefinition => *b"cdef",
Self::Resolution => *b"res ",
Self::CaptureResolution => *b"resc",
Self::DisplayResolution => *b"resd",
Self::ContiguousCodestream => *b"jp2c",
Self::Xml => *b"xml ",
Self::Uuid => *b"uuid",
Self::UuidInfo => *b"uinf",
Self::UuidList => *b"ulst",
Self::Url => *b"url ",
Self::IntellectualProperty => *b"iprp",
Self::Unknown(bytes) => *bytes,
}
}
pub fn as_str(&self) -> String {
let bytes = self.to_bytes();
String::from_utf8_lossy(&bytes).to_string()
}
}
#[derive(Debug, Clone)]
pub struct BoxHeader {
pub box_type: BoxType,
pub length: u64,
pub data_offset: u64,
}
impl BoxHeader {
pub const STANDARD_HEADER_SIZE: u64 = 8;
pub const EXTENDED_HEADER_SIZE: u64 = 16;
pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
let length_u32 = reader.read_u32::<BigEndian>()?;
let mut type_bytes = [0u8; 4];
reader.read_exact(&mut type_bytes)?;
let box_type = BoxType::from_bytes(&type_bytes);
let (length, data_offset) = if length_u32 == 1 {
let length = reader.read_u64::<BigEndian>()?;
(length, Self::EXTENDED_HEADER_SIZE)
} else {
(u64::from(length_u32), Self::STANDARD_HEADER_SIZE)
};
Ok(Self {
box_type,
length,
data_offset,
})
}
pub fn data_size(&self) -> u64 {
self.length.saturating_sub(self.data_offset)
}
}
pub struct BoxReader<R> {
reader: R,
position: u64,
}
impl<R: Read + Seek> BoxReader<R> {
pub fn new(mut reader: R) -> Result<Self> {
let mut sig = [0u8; 12];
reader.read_exact(&mut sig)?;
if sig != JP2_SIGNATURE {
return Err(Jpeg2000Error::InvalidSignature);
}
Ok(Self {
reader,
position: 12,
})
}
pub fn next_box(&mut self) -> Result<Option<BoxHeader>> {
match BoxHeader::read(&mut self.reader) {
Ok(header) => {
self.position += header.data_offset;
Ok(Some(header))
}
Err(Jpeg2000Error::IoError(ref e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
Ok(None)
}
Err(e) => Err(e),
}
}
pub fn read_box_data(&mut self, header: &BoxHeader) -> Result<Vec<u8>> {
let data_size = header.data_size();
if data_size > usize::MAX as u64 {
return Err(Jpeg2000Error::AllocationError(format!(
"Box data size too large: {}",
data_size
)));
}
let mut data = vec![0u8; data_size as usize];
self.reader.read_exact(&mut data)?;
self.position += data_size;
Ok(data)
}
pub fn skip_box(&mut self, header: &BoxHeader) -> Result<()> {
let data_size = header.data_size();
self.reader.seek(SeekFrom::Current(data_size as i64))?;
self.position += data_size;
Ok(())
}
pub fn find_box(&mut self, target_type: BoxType) -> Result<Option<BoxHeader>> {
loop {
match self.next_box()? {
Some(header) => {
if header.box_type == target_type {
return Ok(Some(header));
}
self.skip_box(&header)?;
}
None => return Ok(None),
}
}
}
pub fn reset(&mut self) -> Result<()> {
self.reader.seek(SeekFrom::Start(12))?;
self.position = 12;
Ok(())
}
pub fn position(&self) -> u64 {
self.position
}
}
pub struct BoxWriter<W> {
writer: W,
position: u64,
}
impl<W: Write + Seek> BoxWriter<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
position: 0,
}
}
pub fn write_signature(&mut self) -> Result<()> {
self.writer.write_all(&JP2_SIGNATURE)?;
self.position += 12;
Ok(())
}
pub fn write_file_type(&mut self) -> Result<()> {
let data_size: u32 = 12;
let box_length = 8 + data_size;
self.write_u32_be(box_length)?;
self.writer.write_all(b"ftyp")?;
self.writer.write_all(b"jp2 ")?;
self.write_u32_be(0)?;
self.writer.write_all(b"jp2 ")?;
self.position += box_length as u64;
Ok(())
}
pub fn write_jp2_header(
&mut self,
width: u32,
height: u32,
num_components: u16,
bits_per_component: u8,
color_space: u32,
) -> Result<()> {
let ihdr_data_size = 14u32; let ihdr_box_size = 8 + ihdr_data_size;
let colr_data_size = 7u32; let colr_box_size = 8 + colr_data_size;
let jp2h_data_size = ihdr_box_size + colr_box_size;
let jp2h_box_size = 8 + jp2h_data_size;
self.write_u32_be(jp2h_box_size)?;
self.writer.write_all(b"jp2h")?;
self.write_u32_be(ihdr_box_size)?;
self.writer.write_all(b"ihdr")?;
self.write_u32_be(height)?;
self.write_u32_be(width)?;
self.write_u16_be(num_components)?;
self.writer
.write_all(&[bits_per_component.saturating_sub(1)])?;
self.writer.write_all(&[7])?;
self.writer.write_all(&[0])?;
self.writer.write_all(&[0])?;
self.write_u32_be(colr_box_size)?;
self.writer.write_all(b"colr")?;
self.writer.write_all(&[1])?;
self.writer.write_all(&[0])?;
self.writer.write_all(&[0])?;
self.write_u32_be(color_space)?;
self.position += jp2h_box_size as u64;
Ok(())
}
pub fn write_codestream_box(&mut self, codestream: &[u8]) -> Result<()> {
let data_len = codestream.len() as u64;
if data_len > u32::MAX as u64 - 8 {
let box_length: u64 = 16 + data_len; self.write_u32_be(1)?; self.writer.write_all(b"jp2c")?;
self.write_u64_be(box_length)?;
} else {
let box_length = 8 + data_len as u32;
self.write_u32_be(box_length)?;
self.writer.write_all(b"jp2c")?;
}
self.writer.write_all(codestream)?;
self.position += data_len;
Ok(())
}
pub fn write_xml_box(&mut self, xml_content: &str) -> Result<()> {
let data = xml_content.as_bytes();
let box_length = 8 + data.len() as u32;
self.write_u32_be(box_length)?;
self.writer.write_all(b"xml ")?;
self.writer.write_all(data)?;
self.position += box_length as u64;
Ok(())
}
pub fn write_uuid_box(&mut self, uuid: &[u8; 16], data: &[u8]) -> Result<()> {
let box_length = 8 + 16 + data.len() as u32;
self.write_u32_be(box_length)?;
self.writer.write_all(b"uuid")?;
self.writer.write_all(uuid)?;
self.writer.write_all(data)?;
self.position += box_length as u64;
Ok(())
}
pub fn flush(&mut self) -> Result<()> {
self.writer.flush()?;
Ok(())
}
pub fn position(&self) -> u64 {
self.position
}
fn write_u32_be(&mut self, value: u32) -> Result<()> {
use byteorder::WriteBytesExt;
self.writer.write_u32::<BigEndian>(value)?;
Ok(())
}
fn write_u16_be(&mut self, value: u16) -> Result<()> {
use byteorder::WriteBytesExt;
self.writer.write_u16::<BigEndian>(value)?;
Ok(())
}
fn write_u64_be(&mut self, value: u64) -> Result<()> {
use byteorder::WriteBytesExt;
self.writer.write_u64::<BigEndian>(value)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct RoiRegion {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
pub shift: u8,
pub component: Option<u16>,
}
impl RoiRegion {
pub fn new(x: u32, y: u32, width: u32, height: u32, shift: u8) -> Self {
Self {
x,
y,
width,
height,
shift,
component: None,
}
}
pub fn for_component(
x: u32,
y: u32,
width: u32,
height: u32,
shift: u8,
component: u16,
) -> Self {
Self {
x,
y,
width,
height,
shift,
component: Some(component),
}
}
pub fn contains(&self, px: u32, py: u32) -> bool {
px >= self.x && px < self.x + self.width && py >= self.y && py < self.y + self.height
}
}
#[derive(Debug)]
pub struct RoiEncoder {
regions: Vec<RoiRegion>,
image_width: u32,
image_height: u32,
}
impl RoiEncoder {
pub fn new(image_width: u32, image_height: u32) -> Self {
Self {
regions: Vec::new(),
image_width,
image_height,
}
}
pub fn add_region(&mut self, region: RoiRegion) -> Result<()> {
if region.x + region.width > self.image_width
|| region.y + region.height > self.image_height
{
return Err(Jpeg2000Error::InvalidDimension(format!(
"ROI region ({},{})+({}x{}) exceeds image bounds ({}x{})",
region.x,
region.y,
region.width,
region.height,
self.image_width,
self.image_height
)));
}
if region.shift > 31 {
return Err(Jpeg2000Error::Other(
"ROI shift value must be 0-31".to_string(),
));
}
self.regions.push(region);
Ok(())
}
pub fn generate_mask(&self, component: u16) -> Vec<bool> {
let total = self.image_width as usize * self.image_height as usize;
let mut mask = vec![false; total];
for region in &self.regions {
if let Some(comp) = region.component {
if comp != component {
continue;
}
}
for py in region.y..(region.y + region.height) {
for px in region.x..(region.x + region.width) {
if py < self.image_height && px < self.image_width {
let idx = py as usize * self.image_width as usize + px as usize;
if idx < mask.len() {
mask[idx] = true;
}
}
}
}
}
mask
}
pub fn apply_maxshift_i32(
&self,
coefficients: &mut [i32],
width: usize,
height: usize,
component: u16,
) -> Result<()> {
if coefficients.len() != width * height {
return Err(Jpeg2000Error::WaveletError(format!(
"Coefficient buffer size mismatch: expected {}, got {}",
width * height,
coefficients.len()
)));
}
for region in &self.regions {
if let Some(comp) = region.component {
if comp != component {
continue;
}
}
let shift = region.shift as u32;
for py in region.y..(region.y + region.height) {
for px in region.x..(region.x + region.width) {
let ux = px as usize;
let uy = py as usize;
if ux < width && uy < height {
let idx = uy * width + ux;
if idx < coefficients.len() {
coefficients[idx] = coefficients[idx]
.saturating_mul(1i32.checked_shl(shift).unwrap_or(i32::MAX));
}
}
}
}
}
Ok(())
}
pub fn apply_maxshift_f32(
&self,
coefficients: &mut [f32],
width: usize,
height: usize,
component: u16,
) -> Result<()> {
if coefficients.len() != width * height {
return Err(Jpeg2000Error::WaveletError(format!(
"Coefficient buffer size mismatch: expected {}, got {}",
width * height,
coefficients.len()
)));
}
for region in &self.regions {
if let Some(comp) = region.component {
if comp != component {
continue;
}
}
let scale = (1u32 << region.shift) as f32;
for py in region.y..(region.y + region.height) {
for px in region.x..(region.x + region.width) {
let ux = px as usize;
let uy = py as usize;
if ux < width && uy < height {
let idx = uy * width + ux;
if idx < coefficients.len() {
coefficients[idx] *= scale;
}
}
}
}
}
Ok(())
}
pub fn remove_maxshift_i32(
&self,
coefficients: &mut [i32],
width: usize,
height: usize,
component: u16,
) -> Result<()> {
if coefficients.len() != width * height {
return Err(Jpeg2000Error::WaveletError(format!(
"Coefficient buffer size mismatch: expected {}, got {}",
width * height,
coefficients.len()
)));
}
for region in &self.regions {
if let Some(comp) = region.component {
if comp != component {
continue;
}
}
let shift = region.shift as u32;
for py in region.y..(region.y + region.height) {
for px in region.x..(region.x + region.width) {
let ux = px as usize;
let uy = py as usize;
if ux < width && uy < height {
let idx = uy * width + ux;
if idx < coefficients.len() {
coefficients[idx] >>= shift;
}
}
}
}
}
Ok(())
}
pub fn max_shift(&self) -> u8 {
self.regions.iter().map(|r| r.shift).max().unwrap_or(0)
}
pub fn num_regions(&self) -> usize {
self.regions.len()
}
pub fn to_rgn_markers(&self) -> Vec<(u16, Vec<u8>)> {
let mut markers = Vec::new();
for region in &self.regions {
let component = region.component.unwrap_or(0);
let data = vec![0u8, region.shift];
markers.push((component, data));
}
markers
}
}
use std::io::Write;
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_box_type_conversion() {
let type_bytes = b"jp2h";
let box_type = BoxType::from_bytes(type_bytes);
assert_eq!(box_type, BoxType::Jp2Header);
assert_eq!(box_type.to_bytes(), *type_bytes);
}
#[test]
fn test_signature_validation() {
let data = JP2_SIGNATURE.to_vec();
let cursor = Cursor::new(data);
let result = BoxReader::new(cursor);
assert!(result.is_ok());
}
#[test]
fn test_invalid_signature() {
let data = vec![0u8; 12];
let cursor = Cursor::new(data);
let result = BoxReader::new(cursor);
assert!(result.is_err());
}
#[test]
fn test_write_signature() {
let mut buffer = Cursor::new(Vec::new());
let mut writer = BoxWriter::new(&mut buffer);
writer.write_signature().expect("write signature failed");
let data = buffer.into_inner();
assert_eq!(&data, &JP2_SIGNATURE);
}
#[test]
fn test_write_file_type() {
let mut buffer = Cursor::new(Vec::new());
let mut writer = BoxWriter::new(&mut buffer);
writer.write_file_type().expect("write ftyp failed");
let data = buffer.into_inner();
assert_eq!(data.len(), 20);
assert_eq!(&data[4..8], b"ftyp");
assert_eq!(&data[8..12], b"jp2 ");
}
#[test]
fn test_write_jp2_header() {
let mut buffer = Cursor::new(Vec::new());
let mut writer = BoxWriter::new(&mut buffer);
writer
.write_jp2_header(256, 256, 3, 8, 16) .expect("write jp2h failed");
let data = buffer.into_inner();
assert_eq!(data.len(), 45);
assert_eq!(&data[4..8], b"jp2h");
assert_eq!(&data[12..16], b"ihdr");
}
#[test]
fn test_write_complete_jp2() {
let mut buffer = Cursor::new(Vec::new());
let mut writer = BoxWriter::new(&mut buffer);
writer.write_signature().expect("sig");
writer.write_file_type().expect("ftyp");
writer.write_jp2_header(64, 64, 3, 8, 16).expect("jp2h");
let codestream = vec![0xFF, 0x4F, 0xFF, 0xD9];
writer.write_codestream_box(&codestream).expect("jp2c");
let data = buffer.into_inner();
assert!(data.len() > 12);
assert_eq!(&data[4..8], b"jP ");
}
#[test]
fn test_write_xml_box() {
let mut buffer = Cursor::new(Vec::new());
let mut writer = BoxWriter::new(&mut buffer);
writer
.write_xml_box("<metadata>test</metadata>")
.expect("xml failed");
let data = buffer.into_inner();
assert_eq!(&data[4..8], b"xml ");
}
#[test]
fn test_roi_region_creation() {
let roi = RoiRegion::new(10, 20, 50, 30, 8);
assert_eq!(roi.x, 10);
assert_eq!(roi.y, 20);
assert_eq!(roi.width, 50);
assert_eq!(roi.height, 30);
assert_eq!(roi.shift, 8);
assert!(roi.component.is_none());
}
#[test]
fn test_roi_region_contains() {
let roi = RoiRegion::new(10, 10, 20, 20, 5);
assert!(roi.contains(15, 15));
assert!(roi.contains(10, 10));
assert!(roi.contains(29, 29));
assert!(!roi.contains(30, 15));
assert!(!roi.contains(5, 5));
}
#[test]
fn test_roi_encoder_creation() {
let encoder = RoiEncoder::new(256, 256);
assert_eq!(encoder.num_regions(), 0);
assert_eq!(encoder.max_shift(), 0);
}
#[test]
fn test_roi_add_region() {
let mut encoder = RoiEncoder::new(256, 256);
let result = encoder.add_region(RoiRegion::new(10, 10, 50, 50, 8));
assert!(result.is_ok());
assert_eq!(encoder.num_regions(), 1);
assert_eq!(encoder.max_shift(), 8);
}
#[test]
fn test_roi_invalid_region() {
let mut encoder = RoiEncoder::new(256, 256);
let result = encoder.add_region(RoiRegion::new(200, 200, 100, 100, 5));
assert!(result.is_err()); }
#[test]
fn test_roi_invalid_shift() {
let mut encoder = RoiEncoder::new(256, 256);
let result = encoder.add_region(RoiRegion::new(0, 0, 10, 10, 32));
assert!(result.is_err()); }
#[test]
fn test_roi_generate_mask() {
let mut encoder = RoiEncoder::new(4, 4);
encoder
.add_region(RoiRegion::new(1, 1, 2, 2, 5))
.expect("add region");
let mask = encoder.generate_mask(0);
assert_eq!(mask.len(), 16);
assert!(!mask[0]); assert!(!mask[1]); assert!(mask[5]); assert!(mask[6]); assert!(mask[9]); assert!(mask[10]); assert!(!mask[15]); }
#[test]
fn test_roi_maxshift_i32() {
let mut encoder = RoiEncoder::new(4, 4);
encoder
.add_region(RoiRegion::new(0, 0, 2, 2, 3))
.expect("add region");
let mut coeffs = vec![1i32; 16];
encoder
.apply_maxshift_i32(&mut coeffs, 4, 4, 0)
.expect("apply maxshift");
assert_eq!(coeffs[0], 8); assert_eq!(coeffs[1], 8); assert_eq!(coeffs[4], 8); assert_eq!(coeffs[5], 8);
assert_eq!(coeffs[2], 1); assert_eq!(coeffs[15], 1); }
#[test]
fn test_roi_maxshift_round_trip_i32() {
let mut encoder = RoiEncoder::new(4, 4);
encoder
.add_region(RoiRegion::new(0, 0, 2, 2, 4))
.expect("add region");
let mut coeffs = vec![
10i32, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160,
];
let original_roi_values: Vec<i32> = vec![coeffs[0], coeffs[1], coeffs[4], coeffs[5]];
encoder
.apply_maxshift_i32(&mut coeffs, 4, 4, 0)
.expect("apply");
encoder
.remove_maxshift_i32(&mut coeffs, 4, 4, 0)
.expect("remove");
assert_eq!(coeffs[0], original_roi_values[0]);
assert_eq!(coeffs[1], original_roi_values[1]);
assert_eq!(coeffs[4], original_roi_values[2]);
assert_eq!(coeffs[5], original_roi_values[3]);
}
#[test]
fn test_roi_maxshift_f32() {
let mut encoder = RoiEncoder::new(4, 4);
encoder
.add_region(RoiRegion::new(0, 0, 2, 2, 2))
.expect("add region");
let mut coeffs = vec![1.0f32; 16];
encoder
.apply_maxshift_f32(&mut coeffs, 4, 4, 0)
.expect("apply");
assert!((coeffs[0] - 4.0).abs() < f32::EPSILON);
assert!((coeffs[2] - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_roi_component_specific() {
let mut encoder = RoiEncoder::new(4, 4);
encoder
.add_region(RoiRegion::for_component(0, 0, 2, 2, 3, 1))
.expect("add region");
let mask_c0 = encoder.generate_mask(0);
assert!(mask_c0.iter().all(|&m| !m));
let mask_c1 = encoder.generate_mask(1);
assert!(mask_c1[0]); }
#[test]
fn test_roi_to_rgn_markers() {
let mut encoder = RoiEncoder::new(256, 256);
encoder
.add_region(RoiRegion::new(10, 10, 50, 50, 8))
.expect("add region");
let markers = encoder.to_rgn_markers();
assert_eq!(markers.len(), 1);
assert_eq!(markers[0].0, 0); assert_eq!(markers[0].1, vec![0, 8]); }
}