#[cfg(feature = "cms")]
use crate::api::MoxCms;
use crate::api::{
JxlColorType, JxlDataFormat, JxlDecoder, JxlDecoderOptions, JxlOutputBuffer, JxlPixelFormat,
ProcessingResult, states,
};
use crate::image::{Image, Rect};
fn testdata_dir() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/testdata")
}
fn decode_jxl(path: &std::path::Path) -> Result<(usize, usize, usize, Vec<u8>), String> {
let data = std::fs::read(path).map_err(|e| format!("read failed: {e}"))?;
let mut input = data.as_slice();
#[cfg(feature = "cms")]
let options = JxlDecoderOptions {
cms: Some(Box::new(MoxCms::new())),
..JxlDecoderOptions::default()
};
#[cfg(not(feature = "cms"))]
let options = JxlDecoderOptions::default();
let mut decoder = JxlDecoder::<states::Initialized>::new(options);
let mut decoder = loop {
match decoder.process(&mut input) {
Ok(ProcessingResult::Complete { result }) => break result,
Ok(ProcessingResult::NeedsMoreInput { fallback, .. }) => {
if input.is_empty() {
return Err("unexpected EOF in header".into());
}
decoder = fallback;
}
Err(e) => return Err(format!("header: {e:?}")),
}
};
let basic_info = decoder.basic_info().clone();
let (width, height) = basic_info.size;
let default_format = decoder.current_pixel_format();
let is_grayscale = matches!(
default_format.color_type,
JxlColorType::Grayscale | JxlColorType::GrayscaleAlpha
);
let has_alpha = basic_info.extra_channels.iter().any(|ec| {
matches!(
ec.ec_type,
crate::headers::extra_channels::ExtraChannel::Alpha
)
});
let (color_type, channels) = match (is_grayscale, has_alpha) {
(true, true) => (JxlColorType::GrayscaleAlpha, 2),
(true, false) => (JxlColorType::Grayscale, 1),
(false, true) => (JxlColorType::Rgba, 4),
(false, false) => (JxlColorType::Rgb, 3),
};
let extra_channel_format = vec![None; basic_info.extra_channels.len()];
decoder.set_pixel_format(JxlPixelFormat {
color_type,
color_data_format: Some(JxlDataFormat::U8 { bit_depth: 8 }),
extra_channel_format,
});
let mut decoder = loop {
match decoder.process(&mut input) {
Ok(ProcessingResult::Complete { result }) => break result,
Ok(ProcessingResult::NeedsMoreInput { fallback, .. }) => {
if input.is_empty() {
return Err("unexpected EOF before frame".into());
}
decoder = fallback;
}
Err(e) => return Err(format!("frame info: {e:?}")),
}
};
let mut output_image =
Image::<u8>::new((width * channels, height)).map_err(|e| format!("alloc: {e:?}"))?;
let mut buffers = vec![JxlOutputBuffer::from_image_rect_mut(
output_image
.get_rect_mut(Rect {
origin: (0, 0),
size: (width * channels, height),
})
.into_raw(),
)];
loop {
match decoder.process(&mut input, &mut buffers) {
Ok(ProcessingResult::Complete { .. }) => break,
Ok(ProcessingResult::NeedsMoreInput { fallback, .. }) => {
if input.is_empty() {
return Err("unexpected EOF in frame".into());
}
decoder = fallback;
}
Err(e) => return Err(format!("frame: {e:?}")),
}
}
let mut pixels = Vec::with_capacity(width * height * channels);
for y in 0..height {
pixels.extend_from_slice(output_image.row(y));
}
Ok((width, height, channels, pixels))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn issue_15_lz77_distance_cluster_after_pad() {
let path = testdata_dir().join("issue-15/akfcrc022_e9_d3.0.jxl");
let (width, height, channels, pixels) = decode_jxl(&path)
.unwrap_or_else(|e| panic!("decode of {} failed: {e}", path.display()));
assert_eq!((width, height), (512, 512));
assert!(
channels == 3 || channels == 4,
"unexpected channels: {channels}"
);
assert_eq!(pixels.len(), width * height * channels);
}
}