use crate::box_reader::{BoxReader, BoxType};
use crate::codestream::{
CodestreamParser, CodingStyle, ImageSize, Marker, Quantization, WaveletTransform,
};
use crate::color::ColorConverter;
use crate::error::{Jpeg2000Error, ResilienceMode, Result};
use crate::metadata::{EnumeratedColorSpace, Jp2Metadata};
use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Cursor as IoCursor, Read, Seek, SeekFrom};
pub struct Jpeg2000Reader<R> {
reader: R,
metadata: Option<Jp2Metadata>,
image_size: Option<ImageSize>,
coding_style: Option<CodingStyle>,
quantization: Option<Quantization>,
is_jp2: bool,
resilience_mode: ResilienceMode,
progressive_state: Option<ProgressiveDecodingState>,
raw_codestream: Option<Vec<u8>>,
}
#[derive(Debug, Clone)]
struct ProgressiveDecodingState {
current_layer: u16,
#[allow(dead_code)]
max_layers: u16,
intermediate_data: Vec<u8>,
#[allow(dead_code)]
width: usize,
#[allow(dead_code)]
height: usize,
}
impl<R: Read + Seek> Jpeg2000Reader<R> {
pub fn new(mut reader: R) -> Result<Self> {
let mut magic = [0u8; 12];
let is_jp2 = match reader.read_exact(&mut magic) {
Ok(()) => {
reader.seek(SeekFrom::Start(0))?;
magic[4..8] == *b"jP "
}
Err(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
reader.seek(SeekFrom::Start(0))?;
let mut min_magic = [0u8; 2];
match reader.read_exact(&mut min_magic) {
Ok(()) => {
reader.seek(SeekFrom::Start(0))?;
false
}
Err(_) => {
return Err(Jpeg2000Error::CodestreamError(
"File too small to be valid JPEG2000".to_string(),
));
}
}
}
Err(e) => return Err(e.into()),
};
Ok(Self {
reader,
metadata: None,
image_size: None,
coding_style: None,
quantization: None,
is_jp2,
resilience_mode: ResilienceMode::default(),
progressive_state: None,
raw_codestream: None,
})
}
pub fn set_resilience_mode(&mut self, mode: ResilienceMode) {
self.resilience_mode = mode;
}
pub fn resilience_mode(&self) -> ResilienceMode {
self.resilience_mode
}
pub fn enable_error_resilience(&mut self) {
self.resilience_mode = ResilienceMode::Basic;
}
pub fn enable_full_error_resilience(&mut self) {
self.resilience_mode = ResilienceMode::Full;
}
pub fn disable_error_resilience(&mut self) {
self.resilience_mode = ResilienceMode::None;
}
pub fn parse_headers(&mut self) -> Result<()> {
if self.is_jp2 {
self.parse_jp2_headers()?;
} else {
self.parse_j2k_headers()?;
}
Ok(())
}
fn parse_jp2_headers(&mut self) -> Result<()> {
self.metadata = Some(Jp2Metadata::parse(&mut self.reader)?);
self.parse_optional_boxes()?;
let mut box_reader = BoxReader::new(&mut self.reader)?;
if let Some(jp2c_header) = box_reader.find_box(BoxType::ContiguousCodestream)? {
let codestream_data = box_reader.read_box_data(&jp2c_header)?;
self.raw_codestream = Some(codestream_data.clone());
let mut parser = CodestreamParser::new(std::io::Cursor::new(&codestream_data));
self.parse_codestream(&mut parser)?;
} else {
return Err(Jpeg2000Error::BoxParseError {
box_type: "jp2c".to_string(),
reason: "Codestream box not found".to_string(),
});
}
Ok(())
}
fn parse_optional_boxes(&mut self) -> Result<()> {
let mut box_reader = BoxReader::new(&mut self.reader)?;
box_reader.reset()?;
if let Some(jp2h_header) = box_reader.find_box(BoxType::Jp2Header)? {
let jp2h_data = box_reader.read_box_data(&jp2h_header)?;
let mut jp2h_cursor = std::io::Cursor::new(&jp2h_data);
let mut sub_reader = BoxReader::new(&mut jp2h_cursor)?;
if let Some(res_header) = sub_reader.find_box(BoxType::Resolution)? {
let res_data = sub_reader.read_box_data(&res_header)?;
let mut res_cursor = std::io::Cursor::new(&res_data);
let mut res_sub_reader = BoxReader::new(&mut res_cursor)?;
if let Some(resc_header) = res_sub_reader.find_box(BoxType::CaptureResolution)? {
let resc_data = res_sub_reader.read_box_data(&resc_header)?;
let mut resc_cursor = std::io::Cursor::new(&resc_data);
if let Some(ref mut metadata) = self.metadata {
metadata.capture_resolution =
Some(crate::metadata::Resolution::parse(&mut resc_cursor)?);
}
}
res_sub_reader.reset()?;
if let Some(resd_header) = res_sub_reader.find_box(BoxType::DisplayResolution)? {
let resd_data = res_sub_reader.read_box_data(&resd_header)?;
let mut resd_cursor = std::io::Cursor::new(&resd_data);
if let Some(ref mut metadata) = self.metadata {
metadata.display_resolution =
Some(crate::metadata::Resolution::parse(&mut resd_cursor)?);
}
}
}
}
box_reader.reset()?;
while let Some(xml_header) = box_reader.find_box(BoxType::Xml)? {
let xml_data = box_reader.read_box_data(&xml_header)?;
let mut xml_cursor = std::io::Cursor::new(&xml_data);
if let Some(ref mut metadata) = self.metadata
&& let Ok(xml_box) =
crate::metadata::XmlMetadata::parse(&mut xml_cursor, xml_header.data_size())
{
metadata.xml_boxes.push(xml_box);
}
}
box_reader.reset()?;
while let Some(uuid_header) = box_reader.find_box(BoxType::Uuid)? {
let uuid_data = box_reader.read_box_data(&uuid_header)?;
let mut uuid_cursor = std::io::Cursor::new(&uuid_data);
if let Some(ref mut metadata) = self.metadata
&& let Ok(uuid_box) =
crate::metadata::UuidBox::parse(&mut uuid_cursor, uuid_header.data_size())
{
metadata.uuid_boxes.push(uuid_box);
}
}
Ok(())
}
fn parse_j2k_headers(&mut self) -> Result<()> {
let mut codestream_data = Vec::new();
self.reader.read_to_end(&mut codestream_data)?;
self.raw_codestream = Some(codestream_data.clone());
let mut parser = CodestreamParser::new(std::io::Cursor::new(&codestream_data));
self.parse_codestream(&mut parser)?;
Ok(())
}
fn parse_codestream<CS: Read>(&mut self, parser: &mut CodestreamParser<CS>) -> Result<()> {
match parser.read_marker() {
Ok(Some(Marker::Soc)) => {}
Ok(Some(m)) => {
if self.resilience_mode.is_enabled() {
tracing::warn!(
"Expected SOC marker, got {:?}, continuing with resilience mode",
m
);
} else {
return Err(Jpeg2000Error::CodestreamError(format!(
"Expected SOC marker, got {:?}",
m
)));
}
}
Ok(None) => {
if self.resilience_mode.is_enabled() {
tracing::warn!(
"Unexpected end of stream at SOC, continuing with resilience mode"
);
} else {
return Err(Jpeg2000Error::CodestreamError(
"Unexpected end of stream".to_string(),
));
}
}
Err(e) => {
if self.resilience_mode.is_enabled() {
tracing::warn!(
"Error reading SOC marker: {}, continuing with resilience mode",
e
);
} else {
return Err(e);
}
}
}
loop {
let marker_result = parser.read_marker();
match marker_result {
Ok(Some(Marker::Siz)) => match parser.parse_siz() {
Ok(siz) => self.image_size = Some(siz),
Err(e) => {
if self.resilience_mode.is_enabled() {
tracing::warn!(
"Error parsing SIZ marker: {}, using error concealment",
e
);
} else {
return Err(e);
}
}
},
Ok(Some(Marker::Cod)) => match parser.parse_cod() {
Ok(cod) => self.coding_style = Some(cod),
Err(e) => {
if self.resilience_mode.is_enabled() {
tracing::warn!("Error parsing COD marker: {}, using defaults", e);
} else {
return Err(e);
}
}
},
Ok(Some(Marker::Qcd)) => match parser.parse_qcd() {
Ok(qcd) => self.quantization = Some(qcd),
Err(e) => {
if self.resilience_mode.is_enabled() {
tracing::warn!("Error parsing QCD marker: {}, using defaults", e);
} else {
return Err(e);
}
}
},
Ok(Some(Marker::Sot)) => {
break;
}
Ok(Some(Marker::Eoc)) => {
break;
}
Ok(Some(marker)) => {
if marker.has_segment() {
match parser.read_segment_length() {
Ok(length) => {
if let Err(e) = parser.skip_segment(length) {
if self.resilience_mode.is_enabled() {
tracing::warn!(
"Error skipping marker segment: {}, continuing",
e
);
} else {
return Err(e);
}
}
}
Err(e) => {
if self.resilience_mode.is_enabled() {
tracing::warn!(
"Error reading segment length: {}, continuing",
e
);
} else {
return Err(e);
}
}
}
}
}
Ok(None) => break,
Err(e) => {
if self.resilience_mode.is_enabled() {
tracing::warn!("Error reading marker: {}, attempting to continue", e);
break;
} else {
return Err(e);
}
}
}
}
if self.image_size.is_none() {
if self.resilience_mode.is_full() {
tracing::warn!("SIZ marker not found, using error concealment with default size");
} else {
return Err(Jpeg2000Error::CodestreamError(
"SIZ marker not found".to_string(),
));
}
}
Ok(())
}
pub fn width(&self) -> Result<u32> {
if let Some(ref size) = self.image_size {
Ok(size.width)
} else if let Some(ref metadata) = self.metadata {
metadata
.image_header
.as_ref()
.map(|h| h.width)
.ok_or_else(|| Jpeg2000Error::InvalidImageHeader("No image header".to_string()))
} else {
Err(Jpeg2000Error::InvalidImageHeader(
"Image size not available".to_string(),
))
}
}
pub fn height(&self) -> Result<u32> {
if let Some(ref size) = self.image_size {
Ok(size.height)
} else if let Some(ref metadata) = self.metadata {
metadata
.image_header
.as_ref()
.map(|h| h.height)
.ok_or_else(|| Jpeg2000Error::InvalidImageHeader("No image header".to_string()))
} else {
Err(Jpeg2000Error::InvalidImageHeader(
"Image size not available".to_string(),
))
}
}
pub fn num_components(&self) -> Result<u16> {
if let Some(ref size) = self.image_size {
Ok(size.num_components)
} else if let Some(ref metadata) = self.metadata {
metadata
.image_header
.as_ref()
.map(|h| h.num_components)
.ok_or_else(|| Jpeg2000Error::InvalidImageHeader("No image header".to_string()))
} else {
Err(Jpeg2000Error::InvalidImageHeader(
"Image size not available".to_string(),
))
}
}
pub fn metadata(&self) -> Option<&Jp2Metadata> {
self.metadata.as_ref()
}
fn find_tile_bitstream(&self, tile_index: u32) -> Result<Vec<u8>> {
let codestream = self.raw_codestream.as_ref().ok_or_else(|| {
Jpeg2000Error::CodestreamError("No raw codestream stored".to_string())
})?;
let mut cursor = IoCursor::new(codestream.as_slice());
let soc = cursor
.read_u16::<BigEndian>()
.map_err(|e| Jpeg2000Error::CodestreamError(format!("Read SOC: {}", e)))?;
if soc != 0xFF4F {
return Err(Jpeg2000Error::CodestreamError(format!(
"Expected SOC 0xFF4F, got 0x{:04X}",
soc
)));
}
loop {
let marker_val = cursor
.read_u16::<BigEndian>()
.map_err(|e| Jpeg2000Error::CodestreamError(format!("Read marker: {}", e)))?;
match marker_val {
0xFF90 => {
let lsot = cursor
.read_u16::<BigEndian>()
.map_err(|e| Jpeg2000Error::CodestreamError(format!("Read Lsot: {}", e)))?;
let isot = cursor
.read_u16::<BigEndian>()
.map_err(|e| Jpeg2000Error::CodestreamError(format!("Read Isot: {}", e)))?;
let psot = cursor
.read_u32::<BigEndian>()
.map_err(|e| Jpeg2000Error::CodestreamError(format!("Read Psot: {}", e)))?;
let _tpsot = cursor.read_u8().map_err(|e| {
Jpeg2000Error::CodestreamError(format!("Read TPsot: {}", e))
})?;
let _tnsot = cursor.read_u8().map_err(|e| {
Jpeg2000Error::CodestreamError(format!("Read TNsot: {}", e))
})?;
if u32::from(isot) != tile_index {
if psot > 0 {
let cur_pos = cursor.position() as usize;
let sot_start =
cur_pos.saturating_sub(usize::from(lsot)).saturating_sub(2);
let tile_end = sot_start + psot as usize;
cursor.set_position(tile_end as u64);
}
continue;
}
loop {
let inner = cursor.read_u16::<BigEndian>().map_err(|e| {
Jpeg2000Error::CodestreamError(format!(
"Read tile header marker: {}",
e
))
})?;
match inner {
0xFF93 => {
let sod_pos = cursor.position() as usize;
let final_end = codestream.len().saturating_sub(2);
let end = final_end.min(codestream.len());
return Ok(codestream[sod_pos..end].to_vec());
}
0xFFD9 => {
return Ok(Vec::new());
}
_ => {
let seg_len = cursor.read_u16::<BigEndian>().map_err(|e| {
Jpeg2000Error::CodestreamError(format!("Read seg len: {}", e))
})?;
if seg_len >= 2 {
cursor
.seek(SeekFrom::Current(i64::from(seg_len) - 2))
.map_err(|e| {
Jpeg2000Error::CodestreamError(format!("Seek: {}", e))
})?;
}
}
}
}
}
0xFFD9 => {
return Err(Jpeg2000Error::CodestreamError(format!(
"Tile {} not found before EOC",
tile_index
)));
}
_ => {
if marker_val & 0xFF00 == 0xFF00 && marker_val != 0xFF4F {
let len = cursor.read_u16::<BigEndian>().map_err(|e| {
Jpeg2000Error::CodestreamError(format!("Read main hdr len: {}", e))
})?;
if len >= 2 {
cursor
.seek(SeekFrom::Current(i64::from(len) - 2))
.map_err(|e| {
Jpeg2000Error::CodestreamError(format!("Seek main hdr: {}", e))
})?;
}
}
}
}
}
}
fn decode_tile_to_components(&self, _tile_x: u32, _tile_y: u32) -> Result<Vec<Vec<i32>>> {
let image_size = self
.image_size
.as_ref()
.ok_or_else(|| Jpeg2000Error::InvalidImageHeader("No image size".to_string()))?;
let coding_style = self
.coding_style
.as_ref()
.ok_or_else(|| Jpeg2000Error::CodestreamError("No coding style".to_string()))?;
if coding_style.wavelet == WaveletTransform::Irreversible97 {
return Err(Jpeg2000Error::UnsupportedFeature(
"9/7 irreversible wavelet decode is not yet implemented; use reversible 5/3 encoding".to_string()
));
}
let num_components = image_size.num_components as usize;
let tile_w = image_size.tile_width as usize;
let tile_h = image_size.tile_height as usize;
let num_levels = u32::from(coding_style.num_levels);
let cbw = coding_style.code_block_width_px();
let cbh = coding_style.code_block_height_px();
let tile_data = self.find_tile_bitstream(0)?;
let mut comp_inputs = Vec::with_capacity(num_components);
for comp in 0..num_components {
let dx = usize::from(image_size.components.get(comp).map(|c| c.dx).unwrap_or(1)).max(1);
let dy = usize::from(image_size.components.get(comp).map(|c| c.dy).unwrap_or(1)).max(1);
let comp_w = tile_w.div_ceil(dx).max(1);
let comp_h = tile_h.div_ceil(dy).max(1);
let precision = image_size
.components
.get(comp)
.map(|c| c.precision)
.unwrap_or(8);
comp_inputs.push(crate::tier2::tile::TileComponentInput {
comp_w,
comp_h,
precision,
});
}
let guard_bits = self
.quantization
.as_ref()
.map(|q| q.guard_bits)
.unwrap_or(2);
let params = crate::tier2::tile::TileDecodeParams {
components: &comp_inputs,
num_levels,
cbw,
cbh,
progression: coding_style.progression_order,
num_layers: coding_style.num_layers,
guard_bits,
quantization: self.quantization.as_ref(),
has_sop: coding_style.has_sop,
has_eph: coding_style.has_eph,
};
let mut component_coeffs = crate::tier2::tile::decode_tile_components(&tile_data, ¶ms)?;
if coding_style.use_mct && num_components >= 3 {
ColorConverter::apply_rct(&mut component_coeffs)
.map_err(|e| Jpeg2000Error::CodestreamError(format!("RCT failed: {:?}", e)))?;
}
Ok(component_coeffs)
}
pub fn decode_rgb(&mut self) -> Result<Vec<u8>> {
let width = self.width()? as usize;
let height = self.height()? as usize;
let num_components = self.num_components()? as usize;
if self.raw_codestream.is_none() {
tracing::warn!("decode_rgb called before parse_headers; returning neutral gray");
return Ok(vec![128u8; width * height * 3]);
}
let component_samples = self.decode_tile_to_components(0, 0)?;
let precision = self
.image_size
.as_ref()
.and_then(|s| s.components.first())
.map(|c| c.precision)
.unwrap_or(8);
let is_signed = self
.image_size
.as_ref()
.and_then(|s| s.components.first())
.map(|c| c.is_signed)
.unwrap_or(false);
let shifted: Vec<Vec<u8>> = component_samples
.iter()
.map(|comp| crate::color::level_shift(comp, precision, is_signed))
.collect();
let num_pixels = width * height;
let mut rgb = vec![128u8; num_pixels * 3];
if num_components >= 3 && shifted.len() >= 3 {
for i in 0..num_pixels {
rgb[i * 3] = shifted[0].get(i).copied().unwrap_or(128);
rgb[i * 3 + 1] = shifted[1].get(i).copied().unwrap_or(128);
rgb[i * 3 + 2] = shifted[2].get(i).copied().unwrap_or(128);
}
} else if !shifted.is_empty() {
for i in 0..num_pixels {
let gray = shifted[0].get(i).copied().unwrap_or(128);
rgb[i * 3] = gray;
rgb[i * 3 + 1] = gray;
rgb[i * 3 + 2] = gray;
}
}
Ok(rgb)
}
pub fn decode_rgba(&mut self) -> Result<Vec<u8>> {
let rgb = self.decode_rgb()?;
let num_pixels = rgb.len() / 3;
let mut rgba = Vec::with_capacity(num_pixels * 4);
for i in 0..num_pixels {
rgba.push(rgb[i * 3]);
rgba.push(rgb[i * 3 + 1]);
rgba.push(rgb[i * 3 + 2]);
rgba.push(255);
}
Ok(rgba)
}
pub fn decode_tile(&mut self, tile_x: u32, tile_y: u32) -> Result<Vec<u8>> {
{
let image_size = self.image_size.as_ref().ok_or_else(|| {
Jpeg2000Error::InvalidImageHeader("Image size not available".to_string())
})?;
if tile_x >= image_size.num_tiles_x() || tile_y >= image_size.num_tiles_y() {
return Err(Jpeg2000Error::InvalidTile(format!(
"Tile ({}, {}) out of bounds",
tile_x, tile_y
)));
}
}
if self.raw_codestream.is_none() {
let image_size = self
.image_size
.as_ref()
.ok_or_else(|| Jpeg2000Error::InvalidImageHeader("No image size".to_string()))?;
let tile_width = image_size.tile_width as usize;
let tile_height = image_size.tile_height as usize;
return Ok(vec![128u8; tile_width * tile_height * 3]);
}
let component_samples = self.decode_tile_to_components(tile_x, tile_y)?;
let precision = self
.image_size
.as_ref()
.and_then(|s| s.components.first())
.map(|c| c.precision)
.unwrap_or(8);
let is_signed = self
.image_size
.as_ref()
.and_then(|s| s.components.first())
.map(|c| c.is_signed)
.unwrap_or(false);
let tile_w = self
.image_size
.as_ref()
.map(|s| s.tile_width as usize)
.unwrap_or(0);
let tile_h = self
.image_size
.as_ref()
.map(|s| s.tile_height as usize)
.unwrap_or(0);
let num_components = self.num_components()? as usize;
let shifted: Vec<Vec<u8>> = component_samples
.iter()
.map(|comp| crate::color::level_shift(comp, precision, is_signed))
.collect();
let num_pixels = tile_w * tile_h;
let mut rgb = vec![128u8; num_pixels * 3];
if num_components >= 3 && shifted.len() >= 3 {
for i in 0..num_pixels {
rgb[i * 3] = shifted[0].get(i).copied().unwrap_or(128);
rgb[i * 3 + 1] = shifted[1].get(i).copied().unwrap_or(128);
rgb[i * 3 + 2] = shifted[2].get(i).copied().unwrap_or(128);
}
} else if !shifted.is_empty() {
for i in 0..num_pixels {
let gray = shifted[0].get(i).copied().unwrap_or(128);
rgb[i * 3] = gray;
rgb[i * 3 + 1] = gray;
rgb[i * 3 + 2] = gray;
}
}
Ok(rgb)
}
pub fn info(&self) -> Result<ImageInfo> {
let width = self.width()?;
let height = self.height()?;
let num_components = self.num_components()?;
let num_tiles = if let Some(ref size) = self.image_size {
size.num_tiles()
} else {
1
};
let color_space = self
.metadata
.as_ref()
.and_then(|m| m.color_spec.as_ref())
.and_then(|c| c.enum_cs);
let num_levels = self
.coding_style
.as_ref()
.map(|cs| cs.num_levels)
.unwrap_or(0);
Ok(ImageInfo {
width,
height,
num_components,
num_tiles,
color_space,
num_decomposition_levels: num_levels,
is_jp2: self.is_jp2,
})
}
pub fn file_type(&self) -> Option<&crate::metadata::FileType> {
self.metadata.as_ref()?.file_type.as_ref()
}
pub fn image_header(&self) -> Option<&crate::metadata::ImageHeader> {
self.metadata.as_ref()?.image_header.as_ref()
}
pub fn color_specification(&self) -> Option<&crate::metadata::ColorSpecification> {
self.metadata.as_ref()?.color_spec.as_ref()
}
pub fn capture_resolution(&self) -> Option<&crate::metadata::Resolution> {
self.metadata.as_ref()?.capture_resolution.as_ref()
}
pub fn display_resolution(&self) -> Option<&crate::metadata::Resolution> {
self.metadata.as_ref()?.display_resolution.as_ref()
}
pub fn capture_resolution_dpi(&self) -> Option<(f64, f64)> {
self.capture_resolution().map(|r| r.to_dpi())
}
pub fn display_resolution_dpi(&self) -> Option<(f64, f64)> {
self.display_resolution().map(|r| r.to_dpi())
}
pub fn xml_metadata(&self) -> Vec<&crate::metadata::XmlMetadata> {
self.metadata
.as_ref()
.map(|m| m.xml_boxes.iter().collect())
.unwrap_or_default()
}
pub fn uuid_boxes(&self) -> Vec<&crate::metadata::UuidBox> {
self.metadata
.as_ref()
.map(|m| m.uuid_boxes.iter().collect())
.unwrap_or_default()
}
pub fn coding_style(&self) -> Option<&CodingStyle> {
self.coding_style.as_ref()
}
pub fn quantization(&self) -> Option<&Quantization> {
self.quantization.as_ref()
}
pub fn image_size_info(&self) -> Option<&ImageSize> {
self.image_size.as_ref()
}
pub fn uses_mct(&self) -> bool {
self.coding_style
.as_ref()
.map(|cs| cs.use_mct)
.unwrap_or(false)
}
pub fn num_quality_layers(&self) -> u16 {
self.coding_style
.as_ref()
.map(|cs| cs.num_layers)
.unwrap_or(1)
}
pub fn num_decomposition_levels(&self) -> u8 {
self.coding_style
.as_ref()
.map(|cs| cs.num_levels)
.unwrap_or(0)
}
pub fn decode_quality_layers(&mut self, max_layer: u16) -> Result<Vec<u8>> {
let width = self.width()? as usize;
let height = self.height()? as usize;
let num_layers = self.num_quality_layers();
if max_layer >= num_layers {
return Err(Jpeg2000Error::Tier2Error(format!(
"Requested layer {} exceeds available layers {}",
max_layer, num_layers
)));
}
tracing::info!(
"Decoding quality layers 0-{} of {} (progressive)",
max_layer,
num_layers
);
let rgb = self.decode_rgb()?;
self.progressive_state = Some(ProgressiveDecodingState {
current_layer: max_layer,
max_layers: num_layers,
intermediate_data: rgb.clone(),
width,
height,
});
tracing::info!(
"Progressive decode returned real image data for requested layer {} \
(full-quality decode; layer-limited decode is a pending enhancement)",
max_layer
);
Ok(rgb)
}
pub fn decode_progressive(&mut self) -> Result<ProgressiveDecoder<'_, R>> {
let num_layers = self.num_quality_layers();
Ok(ProgressiveDecoder {
reader: self,
current_layer: 0,
max_layers: num_layers,
})
}
pub fn progressive_layer(&self) -> Option<u16> {
self.progressive_state.as_ref().map(|s| s.current_layer)
}
pub fn progressive_data(&self) -> Option<&[u8]> {
self.progressive_state
.as_ref()
.map(|s| s.intermediate_data.as_slice())
}
pub fn reset_progressive_state(&mut self) {
self.progressive_state = None;
}
pub fn is_progressive_active(&self) -> bool {
self.progressive_state.is_some()
}
pub fn decode_region(&mut self, x: u32, y: u32, width: u32, height: u32) -> Result<Vec<u8>> {
let image_width = self.width()?;
let image_height = self.height()?;
if x + width > image_width {
return Err(Jpeg2000Error::InvalidDimension(format!(
"Region x+width ({}) exceeds image width ({})",
x + width,
image_width
)));
}
if y + height > image_height {
return Err(Jpeg2000Error::InvalidDimension(format!(
"Region y+height ({}) exceeds image height ({})",
y + height,
image_height
)));
}
tracing::info!(
"Decoding region: {}x{} at ({}, {}) from {}x{} image",
width,
height,
x,
y,
image_width,
image_height
);
let tiles = self.compute_intersecting_tiles(x, y, width, height)?;
tracing::debug!("Region intersects with {} tiles", tiles.len());
let full_rgb = self.decode_rgb()?;
let full_width = image_width as usize;
let full_height = image_height as usize;
let x_usize = x as usize;
let y_usize = y as usize;
let width_usize = (width as usize).min(full_width.saturating_sub(x_usize));
let height_usize = (height as usize).min(full_height.saturating_sub(y_usize));
if width_usize == 0 || height_usize == 0 {
return Ok(Vec::new());
}
let mut region = vec![0u8; width_usize * height_usize * 3];
for row in 0..height_usize {
let src_row = y_usize + row;
if src_row >= full_height {
break;
}
let src_start = (src_row * full_width + x_usize) * 3;
let dst_start = row * width_usize * 3;
let copy_len = width_usize * 3;
let src_end = (src_start + copy_len).min(full_rgb.len());
if src_start < full_rgb.len() {
let actual_copy = src_end - src_start;
region[dst_start..dst_start + actual_copy]
.copy_from_slice(&full_rgb[src_start..src_end]);
}
}
Ok(region)
}
pub fn decode_region_at_resolution(
&mut self,
x: u32,
y: u32,
width: u32,
height: u32,
resolution_level: u8,
) -> Result<Vec<u8>> {
let max_levels = self.num_decomposition_levels();
if resolution_level > max_levels {
return Err(Jpeg2000Error::InvalidDimension(format!(
"Resolution level {} exceeds maximum decomposition levels {}",
resolution_level, max_levels
)));
}
let scale_factor = 1u32 << resolution_level;
let full_res_x = x * scale_factor;
let full_res_y = y * scale_factor;
let full_res_width = width * scale_factor;
let full_res_height = height * scale_factor;
let image_width = self.width()?;
let image_height = self.height()?;
if full_res_x + full_res_width > image_width || full_res_y + full_res_height > image_height
{
return Err(Jpeg2000Error::InvalidDimension(format!(
"Scaled region ({}x{} at {},{}) exceeds image bounds ({}x{})",
full_res_width, full_res_height, full_res_x, full_res_y, image_width, image_height
)));
}
tracing::info!(
"Decoding region {}x{} at ({},{}) with resolution level {} (scale 1/{})",
width,
height,
x,
y,
resolution_level,
scale_factor
);
self.decode_region(x, y, width, height)
}
fn compute_intersecting_tiles(
&self,
x: u32,
y: u32,
width: u32,
height: u32,
) -> Result<Vec<(u32, u32)>> {
let image_size = self.image_size.as_ref().ok_or_else(|| {
Jpeg2000Error::InvalidImageHeader("Image size not available".to_string())
})?;
let tile_width = image_size.tile_width;
let tile_height = image_size.tile_height;
let tile_x_offset = image_size.tile_x_offset;
let tile_y_offset = image_size.tile_y_offset;
let start_tile_x = if x >= tile_x_offset {
(x - tile_x_offset) / tile_width
} else {
0
};
let start_tile_y = if y >= tile_y_offset {
(y - tile_y_offset) / tile_height
} else {
0
};
let end_tile_x = if x + width >= tile_x_offset {
((x + width - 1 - tile_x_offset) / tile_width).min(image_size.num_tiles_x() - 1)
} else {
0
};
let end_tile_y = if y + height >= tile_y_offset {
((y + height - 1 - tile_y_offset) / tile_height).min(image_size.num_tiles_y() - 1)
} else {
0
};
let mut tiles = Vec::new();
for ty in start_tile_y..=end_tile_y {
for tx in start_tile_x..=end_tile_x {
tiles.push((tx, ty));
}
}
Ok(tiles)
}
pub fn decode_region_from_tiles(
&mut self,
tiles: &[(u32, u32)],
region_x: u32,
region_y: u32,
region_width: u32,
region_height: u32,
) -> Result<Vec<u8>> {
tracing::info!(
"Decoding {} tiles for region {}x{} at ({},{})",
tiles.len(), region_width,
region_height,
region_x,
region_y
);
self.decode_region(region_x, region_y, region_width, region_height)
}
}
pub struct ProgressiveDecoder<'a, R> {
reader: &'a mut Jpeg2000Reader<R>,
current_layer: u16,
max_layers: u16,
}
impl<'a, R: Read + Seek> ProgressiveDecoder<'a, R> {
pub fn next_layer(&mut self) -> Result<Option<Vec<u8>>> {
if self.current_layer >= self.max_layers {
return Ok(None);
}
let data = self.reader.decode_quality_layers(self.current_layer)?;
self.current_layer += 1;
Ok(Some(data))
}
pub fn current_layer(&self) -> u16 {
self.current_layer
}
pub fn total_layers(&self) -> u16 {
self.max_layers
}
pub fn progress(&self) -> f64 {
if self.max_layers == 0 {
1.0
} else {
f64::from(self.current_layer) / f64::from(self.max_layers)
}
}
pub fn is_complete(&self) -> bool {
self.current_layer >= self.max_layers
}
pub fn skip_to_layer(&mut self, layer: u16) -> Result<Vec<u8>> {
if layer >= self.max_layers {
return Err(Jpeg2000Error::Tier2Error(format!(
"Layer {} exceeds maximum {}",
layer, self.max_layers
)));
}
self.current_layer = layer;
self.reader.decode_quality_layers(layer)
}
}
#[derive(Debug, Clone)]
pub struct ImageInfo {
pub width: u32,
pub height: u32,
pub num_components: u16,
pub num_tiles: u32,
pub color_space: Option<EnumeratedColorSpace>,
pub num_decomposition_levels: u8,
pub is_jp2: bool,
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn build_minimal_j2k_4x4_grayscale() -> Vec<u8> {
let mut out: Vec<u8> = Vec::new();
out.extend_from_slice(&[0xFF, 0x4F]);
out.extend_from_slice(&[0xFF, 0x51]);
out.extend_from_slice(&[0x00, 0x29]); out.extend_from_slice(&[0x00, 0x00]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x04]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x04]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x04]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x04]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); out.extend_from_slice(&[0x00, 0x01]); out.push(0x07); out.push(0x01); out.push(0x01);
out.extend_from_slice(&[0xFF, 0x52]);
out.extend_from_slice(&[0x00, 0x0C]); out.push(0x00); out.push(0x00); out.extend_from_slice(&[0x00, 0x01]); out.push(0x00); out.push(0x00); out.push(0x02); out.push(0x02); out.push(0x00); out.push(0x01);
out.extend_from_slice(&[0xFF, 0x5C]);
out.extend_from_slice(&[0x00, 0x04]); out.push(0x00); out.push(0x00);
out.extend_from_slice(&[0xFF, 0x90]);
out.extend_from_slice(&[0x00, 0x0A]); out.extend_from_slice(&[0x00, 0x00]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); out.push(0x00); out.push(0x01);
out.extend_from_slice(&[0xFF, 0x93]);
out.extend_from_slice(&[0xFF, 0xD9]);
out
}
#[test]
fn test_decode_rgb_minimal_j2k_empty_sod() {
let data = build_minimal_j2k_4x4_grayscale();
let cursor = Cursor::new(data);
let mut reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
reader.parse_headers().expect("parse_headers failed");
assert_eq!(reader.width().expect("width"), 4);
assert_eq!(reader.height().expect("height"), 4);
assert_eq!(reader.num_components().expect("num_components"), 1);
assert!(reader.raw_codestream.is_some());
let rgb = reader.decode_rgb().expect("decode_rgb failed");
assert_eq!(rgb.len(), 4 * 4 * 3);
for i in 0..(4 * 4) {
assert_eq!(rgb[i * 3], rgb[i * 3 + 1], "R != G at pixel {}", i);
assert_eq!(rgb[i * 3 + 1], rgb[i * 3 + 2], "G != B at pixel {}", i);
}
}
#[test]
fn test_decode_quality_layers_returns_real_pixels_not_flat_gray() {
let data = build_minimal_j2k_4x4_grayscale();
let cursor = Cursor::new(data);
let mut reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
reader.parse_headers().expect("parse_headers failed");
let layered = reader
.decode_quality_layers(0)
.expect("decode_quality_layers failed");
assert_eq!(layered.len(), 4 * 4 * 3);
assert!(
layered.iter().all(|&p| p == 0),
"decode_quality_layers must return real decoded pixels, not the flat-gray stub"
);
let rgb = reader.decode_rgb().expect("decode_rgb failed");
assert_eq!(layered, rgb, "progressive and full decode must agree");
assert_eq!(reader.progressive_layer(), Some(0));
assert_eq!(reader.progressive_data(), Some(layered.as_slice()));
}
#[test]
fn test_decode_quality_layers_rejects_out_of_range_layer() {
let data = build_minimal_j2k_4x4_grayscale();
let cursor = Cursor::new(data);
let mut reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
reader.parse_headers().expect("parse_headers failed");
assert!(reader.decode_quality_layers(1).is_err());
}
#[test]
fn test_decode_rgb_with_nonempty_packet_data_is_resilient() {
let mut data = build_minimal_j2k_4x4_grayscale();
let eoc_pos = data.len() - 2;
let packet_bytes = [0x80u8, 0x40, 0x55, 0x00, 0x12, 0x34];
data.splice(eoc_pos..eoc_pos, packet_bytes.iter().copied());
let cursor = Cursor::new(data);
let mut reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
reader.parse_headers().expect("parse_headers failed");
let rgb = reader.decode_rgb().expect("decode_rgb failed");
assert_eq!(rgb.len(), 4 * 4 * 3);
}
fn find_marker(data: &[u8], marker: u16) -> Option<usize> {
let hi = (marker >> 8) as u8;
let lo = marker as u8;
data.windows(2).position(|w| w[0] == hi && w[1] == lo)
}
#[test]
fn test_decode_tile_real_packet_matches_tier1() {
use crate::tier1::{CodeBlockDecoder, SubbandType};
use crate::tier2::layout::code_block_bitplanes;
let body: [u8; 5] = [0x95, 0x40, 0x22, 0x0C, 0x71];
let mut packet = vec![0xE5u8];
packet.extend_from_slice(&body);
let mut data = build_minimal_j2k_4x4_grayscale();
let eoc = data.len() - 2; data.splice(eoc..eoc, packet.iter().copied());
let mut reader = Jpeg2000Reader::new(Cursor::new(data)).expect("reader creation failed");
reader.parse_headers().expect("parse_headers failed");
let comps = reader
.decode_tile_to_components(0, 0)
.expect("tile decode failed");
assert_eq!(comps.len(), 1);
let num_bitplanes = code_block_bitplanes(0, 0, 8, 0);
let reference = CodeBlockDecoder::with_subband(4, 4, num_bitplanes, SubbandType::Ll)
.decode(&body)
.expect("reference tier-1 decode failed");
assert_eq!(
comps[0], reference,
"Tier-2 must slice exactly the packet body bytes and feed them to Tier-1"
);
assert_ne!(
reference,
vec![0i32; 16],
"chosen body should decode to some non-zero coefficients"
);
let rgb = reader.decode_rgb().expect("decode_rgb failed");
assert_eq!(rgb.len(), 4 * 4 * 3);
}
#[test]
fn test_decode_multi_layer_codestream_rejected() {
let mut data = build_minimal_j2k_4x4_grayscale();
let cod = find_marker(&data, 0xFF52).expect("COD marker present");
data[cod + 6] = 0x00;
data[cod + 7] = 0x02;
let mut reader = Jpeg2000Reader::new(Cursor::new(data)).expect("reader creation failed");
reader.parse_headers().expect("parse_headers failed");
assert_eq!(reader.num_quality_layers(), 2);
let result = reader.decode_rgb();
assert!(
matches!(result, Err(Jpeg2000Error::UnsupportedFeature(_))),
"multi-layer decode must return UnsupportedFeature, got {:?}",
result.map(|v| v.len())
);
}
#[test]
fn test_decode_unsupported_progression_rejected() {
let mut data = build_minimal_j2k_4x4_grayscale();
let cod = find_marker(&data, 0xFF52).expect("COD marker present");
data[cod + 5] = 0x04;
let mut reader = Jpeg2000Reader::new(Cursor::new(data)).expect("reader creation failed");
reader.parse_headers().expect("parse_headers failed");
let result = reader.decode_rgb();
assert!(
matches!(result, Err(Jpeg2000Error::UnsupportedFeature(_))),
"unsupported progression must return UnsupportedFeature"
);
}
#[test]
fn test_reader_creation() {
let data = vec![
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A, ];
let cursor = Cursor::new(data);
let result = Jpeg2000Reader::new(cursor);
assert!(result.is_ok());
let reader = result.expect("reader failed");
assert!(reader.is_jp2);
}
#[test]
fn test_j2k_detection() {
let data = vec![
0xFF, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let cursor = Cursor::new(data);
let result = Jpeg2000Reader::new(cursor);
assert!(result.is_ok());
let reader = result.expect("reader failed");
assert!(!reader.is_jp2);
}
#[test]
fn test_resilience_mode_default() {
let data = vec![
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
];
let cursor = Cursor::new(data);
let reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
assert_eq!(reader.resilience_mode(), ResilienceMode::None);
assert!(!reader.resilience_mode().is_enabled());
}
#[test]
fn test_resilience_mode_configuration() {
let data = vec![
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
];
let cursor = Cursor::new(data);
let mut reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
reader.enable_error_resilience();
assert_eq!(reader.resilience_mode(), ResilienceMode::Basic);
assert!(reader.resilience_mode().is_enabled());
reader.enable_full_error_resilience();
assert_eq!(reader.resilience_mode(), ResilienceMode::Full);
assert!(reader.resilience_mode().is_full());
reader.disable_error_resilience();
assert_eq!(reader.resilience_mode(), ResilienceMode::None);
}
#[test]
fn test_progressive_state_initialization() {
let data = vec![
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
];
let cursor = Cursor::new(data);
let reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
assert!(!reader.is_progressive_active());
assert!(reader.progressive_layer().is_none());
}
#[test]
fn test_progressive_state_reset() {
let data = vec![
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
];
let cursor = Cursor::new(data);
let mut reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
reader.progressive_state = Some(ProgressiveDecodingState {
current_layer: 2,
max_layers: 5,
intermediate_data: vec![],
width: 256,
height: 256,
});
assert!(reader.is_progressive_active());
assert_eq!(reader.progressive_layer(), Some(2));
reader.reset_progressive_state();
assert!(!reader.is_progressive_active());
assert!(reader.progressive_layer().is_none());
}
#[test]
fn test_region_bounds_validation() {
let data = vec![
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
];
let cursor = Cursor::new(data);
let mut reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
reader.image_size = Some(ImageSize {
width: 256,
height: 256,
x_offset: 0,
y_offset: 0,
tile_width: 256,
tile_height: 256,
tile_x_offset: 0,
tile_y_offset: 0,
num_components: 3,
components: vec![],
});
let result = reader.decode_region(0, 0, 128, 128);
assert!(result.is_ok());
let result = reader.decode_region(200, 0, 100, 128);
assert!(result.is_err());
let result = reader.decode_region(0, 200, 128, 100);
assert!(result.is_err());
}
#[test]
fn test_compute_intersecting_tiles() {
let data = vec![
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
];
let cursor = Cursor::new(data);
let mut reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
reader.image_size = Some(ImageSize {
width: 512,
height: 512,
x_offset: 0,
y_offset: 0,
tile_width: 128,
tile_height: 128,
tile_x_offset: 0,
tile_y_offset: 0,
num_components: 3,
components: vec![],
});
let tiles = reader.compute_intersecting_tiles(0, 0, 64, 64);
assert!(tiles.is_ok());
let tiles = tiles.expect("tiles");
assert_eq!(tiles.len(), 1);
assert_eq!(tiles[0], (0, 0));
let tiles = reader.compute_intersecting_tiles(64, 64, 128, 128);
assert!(tiles.is_ok());
let tiles = tiles.expect("tiles");
assert!(!tiles.is_empty());
let tiles = reader.compute_intersecting_tiles(0, 0, 512, 512);
assert!(tiles.is_ok());
let tiles = tiles.expect("tiles");
assert_eq!(tiles.len(), 16); }
#[test]
fn test_resolution_level_scaling() {
let data = vec![
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
];
let cursor = Cursor::new(data);
let mut reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
reader.image_size = Some(ImageSize {
width: 256,
height: 256,
x_offset: 0,
y_offset: 0,
tile_width: 256,
tile_height: 256,
tile_x_offset: 0,
tile_y_offset: 0,
num_components: 3,
components: vec![],
});
reader.coding_style = Some(CodingStyle {
progression_order: crate::codestream::ProgressionOrder::Lrcp,
num_layers: 5,
use_mct: true,
num_levels: 3,
code_block_width: 64,
code_block_height: 64,
code_block_style: 0,
wavelet: crate::codestream::WaveletTransform::Reversible53,
has_sop: false,
has_eph: false,
});
let result = reader.decode_region_at_resolution(0, 0, 128, 128, 0);
assert!(result.is_ok());
let data = result.expect("data");
assert_eq!(data.len(), 128 * 128 * 3);
let result = reader.decode_region_at_resolution(0, 0, 64, 64, 1);
assert!(result.is_ok());
let data = result.expect("data");
assert_eq!(data.len(), 64 * 64 * 3);
let result = reader.decode_region_at_resolution(0, 0, 64, 64, 10);
assert!(result.is_err());
}
#[test]
fn test_metadata_accessors() {
let data = vec![
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
];
let cursor = Cursor::new(data);
let reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
assert!(reader.file_type().is_none());
assert!(reader.image_header().is_none());
assert!(reader.color_specification().is_none());
assert!(reader.capture_resolution().is_none());
assert!(reader.display_resolution().is_none());
assert!(reader.xml_metadata().is_empty());
assert!(reader.uuid_boxes().is_empty());
}
#[test]
fn test_quality_layer_accessors() {
let data = vec![
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
];
let cursor = Cursor::new(data);
let mut reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
assert_eq!(reader.num_quality_layers(), 1);
reader.coding_style = Some(CodingStyle {
progression_order: crate::codestream::ProgressionOrder::Lrcp,
num_layers: 10,
use_mct: false,
num_levels: 5,
code_block_width: 64,
code_block_height: 64,
code_block_style: 0,
wavelet: crate::codestream::WaveletTransform::Reversible53,
has_sop: false,
has_eph: false,
});
assert_eq!(reader.num_quality_layers(), 10);
assert_eq!(reader.num_decomposition_levels(), 5);
assert!(!reader.uses_mct());
}
#[test]
fn test_progressive_decoder_iterator() {
let data = vec![
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
];
let cursor = Cursor::new(data);
let mut reader = Jpeg2000Reader::new(cursor).expect("reader creation failed");
reader.image_size = Some(ImageSize {
width: 64,
height: 64,
x_offset: 0,
y_offset: 0,
tile_width: 64,
tile_height: 64,
tile_x_offset: 0,
tile_y_offset: 0,
num_components: 3,
components: vec![],
});
reader.coding_style = Some(CodingStyle {
progression_order: crate::codestream::ProgressionOrder::Lrcp,
num_layers: 3,
use_mct: false,
num_levels: 2,
code_block_width: 32,
code_block_height: 32,
code_block_style: 0,
wavelet: crate::codestream::WaveletTransform::Reversible53,
has_sop: false,
has_eph: false,
});
let decoder = reader.decode_progressive().expect("decoder");
assert_eq!(decoder.total_layers(), 3);
assert_eq!(decoder.current_layer(), 0);
assert!(!decoder.is_complete());
assert_eq!(decoder.progress(), 0.0);
}
}