#[cfg(feature = "jpeg2000")]
use crate::error::Error;
use crate::error::Result;
pub struct JpxDecoder;
impl super::StreamDecoder for JpxDecoder {
fn decode(&self, input: &[u8]) -> Result<Vec<u8>> {
Ok(input.to_vec())
}
fn name(&self) -> &str {
"JPXDecode"
}
}
#[cfg(feature = "jpeg2000")]
pub struct JpxImage {
pub samples: Vec<u8>,
pub num_components: u8,
}
#[cfg(feature = "jpeg2000")]
pub fn decode_jpx(bytes: &[u8]) -> Result<JpxImage> {
use hayro_jpeg2000::{DecodeSettings, DecoderContext, Image};
let image = Image::new(bytes, &DecodeSettings::default()).map_err(|e| {
Error::UnsupportedFilter(format!("JPXDecode: JPEG 2000 decode failed: {e:?}"))
})?;
let width = image.width();
let height = image.height();
let npix = width as usize * height as usize;
let mut ctx = DecoderContext::default();
let decoded = image.decode(&mut ctx).map_err(|e| {
Error::UnsupportedFilter(format!("JPXDecode: JPEG 2000 decode failed: {e:?}"))
})?;
let comps = decoded.components();
if comps.is_empty() {
return Err(Error::UnsupportedFilter(
"JPXDecode: JPEG 2000 image has no components".to_string(),
));
}
let num_components = comps.len();
for (ci, comp) in comps.iter().enumerate() {
if comp.samples().len() != npix {
return Err(Error::UnsupportedFilter(format!(
"JPXDecode: subsampled JPEG 2000 component {ci} ({} samples vs \
{width}x{height}={npix}) not supported",
comp.samples().len()
)));
}
}
Ok(JpxImage {
samples: decoded.data_u8(),
num_components: num_components as u8,
})
}
#[cfg(all(test, feature = "jpeg2000"))]
mod tests {
use super::decode_jpx;
const SAMPLE_JP2: &[u8] = include_bytes!("../../tests/fixtures/jpx/sample_gray.jp2");
#[test]
fn decode_jpx_grayscale() {
let img = decode_jpx(SAMPLE_JP2).expect("decode JP2 codestream");
assert_eq!(img.num_components, 1);
assert_eq!(img.samples.len(), 816 * 1056);
let first = img.samples[0];
assert!(
img.samples.iter().any(|&b| b != first),
"decoded image is uniformly flat — decode likely failed"
);
}
}