use super::{
GainMapBundle, JxlBasicInfo, JxlColorProfile, JxlColorType, JxlDataFormat, JxlDecoder,
JxlDecoderLimits, JxlDecoderOptions, JxlOutputBuffer, JxlPixelFormat, ProcessingResult, states,
};
use crate::error::{Error, Result};
use crate::headers::extra_channels::ExtraChannel;
use crate::image::{OwnedRawImage, Rect};
#[non_exhaustive]
pub struct JxlImage {
pub width: usize,
pub height: usize,
pub data: Vec<u8>,
pub channels: usize,
pub is_grayscale: bool,
pub info: JxlBasicInfo,
pub output_profile: JxlColorProfile,
pub embedded_profile: JxlColorProfile,
pub gain_map: Option<GainMapBundle>,
pub exif: Option<Vec<u8>>,
pub xmp: Option<Vec<u8>>,
}
#[non_exhaustive]
pub struct JxlImageInfo {
pub info: JxlBasicInfo,
pub embedded_profile: JxlColorProfile,
}
pub fn decode(data: &[u8]) -> Result<JxlImage> {
decode_with(data, JxlDecoderOptions::default())
}
pub fn decode_with(data: &[u8], options: JxlDecoderOptions) -> Result<JxlImage> {
let mut input: &[u8] = data;
let decoder = JxlDecoder::<states::Initialized>::new(options);
let mut decoder = match decoder.process(&mut input)? {
ProcessingResult::Complete { result } => result,
ProcessingResult::NeedsMoreInput { .. } => {
return Err(Error::OutOfBounds(0));
}
};
let info = decoder.basic_info().clone();
let embedded_profile = decoder.embedded_color_profile().clone();
let (width, height) = info.size;
let is_grayscale = decoder.current_pixel_format().color_type.is_grayscale();
let color_type = if is_grayscale {
JxlColorType::GrayscaleAlpha
} else {
JxlColorType::Rgba
};
let channels = color_type.samples_per_pixel();
let main_alpha = info
.extra_channels
.iter()
.position(|ec| ec.ec_type == ExtraChannel::Alpha);
let u8_format = JxlDataFormat::U8 { bit_depth: 8 };
let pixel_format = JxlPixelFormat {
color_type,
color_data_format: Some(u8_format),
extra_channel_format: info
.extra_channels
.iter()
.enumerate()
.map(|(i, _)| {
if Some(i) == main_alpha {
None } else {
Some(u8_format)
}
})
.collect(),
};
decoder.set_pixel_format(pixel_format);
let output_profile = decoder.output_color_profile().clone();
let extra_count = info.extra_channels.len() - usize::from(main_alpha.is_some());
let decoder = match decoder.process(&mut input)? {
ProcessingResult::Complete { result } => result,
ProcessingResult::NeedsMoreInput { .. } => {
return Err(Error::OutOfBounds(0));
}
};
let row_bytes = width * channels; let mut output = OwnedRawImage::new_uninit((row_bytes, height))?;
#[cfg(feature = "threads")]
output.prefault_parallel();
let mut extra_outputs: Vec<OwnedRawImage> = (0..extra_count)
.map(|_| OwnedRawImage::new_uninit((width, height)))
.collect::<Result<_>>()?;
let mut bufs: Vec<JxlOutputBuffer<'_>> = std::iter::once(&mut output)
.chain(extra_outputs.iter_mut())
.map(|img| {
let rect = Rect {
size: img.byte_size(),
origin: (0, 0),
};
JxlOutputBuffer::from_image_rect_mut(img.get_rect_mut(rect))
})
.collect();
let mut decoder = match decoder.process(&mut input, &mut bufs)? {
ProcessingResult::Complete { result } => result,
ProcessingResult::NeedsMoreInput { .. } => {
return Err(Error::OutOfBounds(0));
}
};
let gain_map = decoder.take_gain_map();
let exif = decoder.take_exif();
let xmp = decoder.take_xmp();
let total_bytes = row_bytes * height;
let mut pixels = Vec::with_capacity(total_bytes);
for y in 0..height {
pixels.extend_from_slice(output.row(y));
}
Ok(JxlImage {
width,
height,
data: pixels,
channels,
is_grayscale,
info,
output_profile,
embedded_profile,
gain_map,
exif,
xmp,
})
}
pub fn read_header(data: &[u8]) -> Result<JxlImageInfo> {
read_header_with(data, JxlDecoderLimits::default())
}
pub fn read_header_with(data: &[u8], limits: JxlDecoderLimits) -> Result<JxlImageInfo> {
let mut input: &[u8] = data;
let options = JxlDecoderOptions {
limits,
..JxlDecoderOptions::default()
};
let decoder = JxlDecoder::<states::Initialized>::new(options);
let decoder = match decoder.process(&mut input)? {
ProcessingResult::Complete { result } => result,
ProcessingResult::NeedsMoreInput { .. } => {
return Err(Error::OutOfBounds(0));
}
};
Ok(JxlImageInfo {
info: decoder.basic_info().clone(),
embedded_profile: decoder.embedded_color_profile().clone(),
})
}