#![cfg_attr(docsrs, feature(doc_cfg))]
use std::sync::Arc;
use jxl_bitstream::{Bitstream, ContainerParser, ParseEvent};
use jxl_frame::FrameContext;
use jxl_image::BitDepth;
use jxl_oxide_common::{Bundle, Name};
use jxl_render::ImageBuffer;
use jxl_render::ImageWithRegion;
use jxl_render::Region;
use jxl_render::{IndexedFrame, RenderContext};
pub use jxl_color::{ColorEncodingWithProfile, ColorManagementSystem, NullCms, PreparedTransform};
pub use jxl_frame::header as frame;
pub use jxl_frame::{Frame, FrameHeader};
pub use jxl_grid::{AlignedGrid, AllocTracker};
pub use jxl_image::color::{self, EnumColourEncoding, RenderingIntent};
pub use jxl_image::{self as image, ExtraChannelType, ImageHeader};
pub use jxl_jbr as jpeg_bitstream;
pub use jxl_threadpool::JxlThreadPool;
mod aux_box;
mod fb;
pub mod integration;
#[cfg(feature = "lcms2")]
mod lcms2;
#[cfg(feature = "moxcms")]
mod moxcms;
#[cfg(feature = "lcms2")]
pub use self::lcms2::Lcms2;
#[cfg(feature = "moxcms")]
pub use self::moxcms::Moxcms;
pub use aux_box::{AuxBoxData, AuxBoxList, RawExif};
pub use fb::{FrameBuffer, FrameBufferSample, ImageStream};
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync + 'static>>;
#[cfg(feature = "rayon")]
fn default_pool() -> JxlThreadPool {
JxlThreadPool::rayon_global()
}
#[cfg(not(feature = "rayon"))]
fn default_pool() -> JxlThreadPool {
JxlThreadPool::none()
}
#[derive(Debug, Default)]
pub struct JxlImageBuilder {
pool: Option<JxlThreadPool>,
tracker: Option<AllocTracker>,
force_wide_buffers: bool,
}
impl JxlImageBuilder {
pub fn pool(mut self, pool: JxlThreadPool) -> Self {
self.pool = Some(pool);
self
}
pub fn alloc_tracker(mut self, tracker: AllocTracker) -> Self {
self.tracker = Some(tracker);
self
}
pub fn force_wide_buffers(mut self, force_wide_buffers: bool) -> Self {
self.force_wide_buffers = force_wide_buffers;
self
}
pub fn build_uninit(self) -> UninitializedJxlImage {
UninitializedJxlImage {
pool: self.pool.unwrap_or_else(default_pool),
tracker: self.tracker,
reader: ContainerParser::new(),
buffer: Vec::new(),
aux_boxes: AuxBoxList::new(),
force_wide_buffers: self.force_wide_buffers,
}
}
pub fn read(self, mut reader: impl std::io::Read) -> Result<JxlImage> {
let mut uninit = self.build_uninit();
let mut buf = vec![0u8; 4096];
let mut buf_valid = 0usize;
let mut image = loop {
let count = reader.read(&mut buf[buf_valid..])?;
if count == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"reader ended before parsing image header",
)
.into());
}
buf_valid += count;
let consumed = uninit.feed_bytes(&buf[..buf_valid])?;
buf.copy_within(consumed..buf_valid, 0);
buf_valid -= consumed;
match uninit.try_init()? {
InitializeResult::NeedMoreData(x) => {
uninit = x;
}
InitializeResult::Initialized(x) => {
break x;
}
}
};
while !image.inner.end_of_image {
let count = reader.read(&mut buf[buf_valid..])?;
if count == 0 {
break;
}
buf_valid += count;
let consumed = image.feed_bytes(&buf[..buf_valid])?;
buf.copy_within(consumed..buf_valid, 0);
buf_valid -= consumed;
}
buf.truncate(buf_valid);
image.finalize()?;
Ok(image)
}
pub fn open(self, path: impl AsRef<std::path::Path>) -> Result<JxlImage> {
let file = std::fs::File::open(path)?;
self.read(file)
}
}
pub struct UninitializedJxlImage {
pool: JxlThreadPool,
tracker: Option<AllocTracker>,
reader: ContainerParser,
buffer: Vec<u8>,
aux_boxes: AuxBoxList,
force_wide_buffers: bool,
}
impl UninitializedJxlImage {
pub fn feed_bytes(&mut self, buf: &[u8]) -> Result<usize> {
for event in self.reader.feed_bytes(buf) {
match event? {
ParseEvent::BitstreamKind(_) => {}
ParseEvent::Codestream(buf) => {
self.buffer.extend_from_slice(buf);
}
aux_box_event => {
self.aux_boxes.handle_event(aux_box_event)?;
}
}
}
Ok(self.reader.previous_consumed_bytes())
}
#[inline]
pub fn reader(&self) -> &ContainerParser {
&self.reader
}
pub fn try_init(mut self) -> Result<InitializeResult> {
let mut bitstream = Bitstream::new(&self.buffer);
let image_header = match ImageHeader::parse(&mut bitstream, ()) {
Ok(x) => x,
Err(e) if e.unexpected_eof() => {
return Ok(InitializeResult::NeedMoreData(self));
}
Err(e) => {
return Err(e.into());
}
};
let embedded_icc = if image_header.metadata.colour_encoding.want_icc() {
let icc = match jxl_color::icc::read_icc(&mut bitstream) {
Ok(x) => x,
Err(e) if e.unexpected_eof() => {
return Ok(InitializeResult::NeedMoreData(self));
}
Err(e) => {
return Err(e.into());
}
};
tracing::debug!("Image has an embedded ICC profile");
let icc = jxl_color::icc::decode_icc(&icc)?;
Some(icc)
} else {
None
};
bitstream.zero_pad_to_byte()?;
let image_header = Arc::new(image_header);
let skip_bytes = if image_header.metadata.preview.is_some() {
let frame = match Frame::parse(
&mut bitstream,
FrameContext {
image_header: image_header.clone(),
tracker: self.tracker.as_ref(),
pool: self.pool.clone(),
},
) {
Ok(x) => x,
Err(e) if e.unexpected_eof() => {
return Ok(InitializeResult::NeedMoreData(self));
}
Err(e) => {
return Err(e.into());
}
};
let bytes_read = bitstream.num_read_bits() / 8;
let x = frame.toc().total_byte_size();
if self.buffer.len() < bytes_read + x {
return Ok(InitializeResult::NeedMoreData(self));
}
x
} else {
0usize
};
let bytes_read = bitstream.num_read_bits() / 8 + skip_bytes;
self.buffer.drain(..bytes_read);
let render_spot_color = !image_header.metadata.grayscale();
let mut builder = RenderContext::builder().pool(self.pool.clone());
if let Some(icc) = embedded_icc {
builder = builder.embedded_icc(icc);
}
if let Some(tracker) = self.tracker {
builder = builder.alloc_tracker(tracker);
}
builder = builder.force_wide_buffers(self.force_wide_buffers);
#[cfg_attr(not(any(feature = "lcms2", feature = "moxcms")), allow(unused_mut))]
let mut ctx = builder.build(image_header.clone())?;
#[cfg(feature = "lcms2")]
ctx.set_cms(Lcms2);
#[cfg(all(not(feature = "lcms2"), feature = "moxcms"))]
ctx.set_cms(Moxcms);
let mut image = JxlImage {
pool: self.pool.clone(),
reader: self.reader,
image_header,
ctx: Box::new(ctx),
render_spot_color,
inner: JxlImageInner {
end_of_image: false,
buffer: Vec::new(),
buffer_offset: bytes_read,
frame_offsets: Vec::new(),
aux_boxes: self.aux_boxes,
},
};
image.inner.feed_bytes_inner(&mut image.ctx, &self.buffer)?;
Ok(InitializeResult::Initialized(image))
}
}
pub enum InitializeResult {
NeedMoreData(UninitializedJxlImage),
Initialized(JxlImage),
}
#[derive(Debug)]
pub struct JxlImage {
pool: JxlThreadPool,
reader: ContainerParser,
image_header: Arc<ImageHeader>,
ctx: Box<RenderContext>,
render_spot_color: bool,
inner: JxlImageInner,
}
impl JxlImage {
pub fn builder() -> JxlImageBuilder {
JxlImageBuilder::default()
}
pub fn read_with_defaults(reader: impl std::io::Read) -> Result<JxlImage> {
Self::builder().read(reader)
}
pub fn open_with_defaults(path: impl AsRef<std::path::Path>) -> Result<JxlImage> {
Self::builder().open(path)
}
pub fn feed_bytes(&mut self, buf: &[u8]) -> Result<usize> {
for event in self.reader.feed_bytes(buf) {
match event? {
ParseEvent::BitstreamKind(_) => {}
ParseEvent::Codestream(buf) => {
self.inner.feed_bytes_inner(&mut self.ctx, buf)?;
}
aux_box_event => {
self.inner.aux_boxes.handle_event(aux_box_event)?;
}
}
}
Ok(self.reader.previous_consumed_bytes())
}
pub fn finalize(&mut self) -> Result<()> {
self.inner.aux_boxes.eof()?;
Ok(())
}
}
impl JxlImage {
#[inline]
pub fn image_header(&self) -> &ImageHeader {
&self.image_header
}
#[inline]
pub fn width(&self) -> u32 {
self.image_header.width_with_orientation()
}
#[inline]
pub fn height(&self) -> u32 {
self.image_header.height_with_orientation()
}
#[inline]
pub fn original_icc(&self) -> Option<&[u8]> {
self.ctx.embedded_icc()
}
pub fn rendered_icc(&self) -> Vec<u8> {
let encoding = self.ctx.requested_color_encoding();
match encoding.encoding() {
color::ColourEncoding::Enum(encoding) => {
jxl_color::icc::colour_encoding_to_icc(encoding)
}
color::ColourEncoding::IccProfile(_) => encoding.icc_profile().to_vec(),
}
}
#[inline]
pub fn rendered_cicp(&self) -> Option<[u8; 4]> {
let encoding = self.ctx.requested_color_encoding();
encoding.encoding().cicp()
}
pub fn pixel_format(&self) -> PixelFormat {
let encoding = self.ctx.requested_color_encoding();
let is_grayscale = encoding.is_grayscale();
let has_black = encoding.is_cmyk();
let mut has_alpha = false;
for ec_info in &self.image_header.metadata.ec_info {
if ec_info.is_alpha() {
has_alpha = true;
}
}
match (is_grayscale, has_black, has_alpha) {
(false, false, false) => PixelFormat::Rgb,
(false, false, true) => PixelFormat::Rgba,
(false, true, false) => PixelFormat::Cmyk,
(false, true, true) => PixelFormat::Cmyka,
(true, _, false) => PixelFormat::Gray,
(true, _, true) => PixelFormat::Graya,
}
}
pub fn hdr_type(&self) -> Option<HdrType> {
self.ctx.suggested_hdr_tf().and_then(|tf| match tf {
color::TransferFunction::Pq => Some(HdrType::Pq),
color::TransferFunction::Hlg => Some(HdrType::Hlg),
_ => None,
})
}
#[inline]
pub fn render_spot_color(&self) -> bool {
self.render_spot_color
}
#[inline]
pub fn set_render_spot_color(&mut self, render_spot_color: bool) -> &mut Self {
if render_spot_color && self.image_header.metadata.grayscale() {
tracing::warn!("Spot colour channels are not rendered on grayscale images");
return self;
}
self.render_spot_color = render_spot_color;
self
}
pub fn aux_boxes(&self) -> &AuxBoxList {
&self.inner.aux_boxes
}
#[inline]
pub fn num_loaded_keyframes(&self) -> usize {
self.ctx.loaded_keyframes()
}
#[inline]
pub fn num_loaded_frames(&self) -> usize {
self.ctx.loaded_frames()
}
#[inline]
pub fn is_loading_done(&self) -> bool {
self.inner.end_of_image
}
pub fn frame_by_keyframe(&self, keyframe_index: usize) -> Option<&IndexedFrame> {
self.ctx.keyframe(keyframe_index)
}
pub fn frame_header(&self, keyframe_index: usize) -> Option<&FrameHeader> {
let frame = self.ctx.keyframe(keyframe_index)?;
Some(frame.header())
}
pub fn frame(&self, frame_idx: usize) -> Option<&IndexedFrame> {
self.ctx.frame(frame_idx)
}
pub fn frame_offset(&self, frame_index: usize) -> Option<usize> {
self.inner.frame_offsets.get(frame_index).copied()
}
#[inline]
pub fn pool(&self) -> &JxlThreadPool {
&self.pool
}
pub fn reader(&self) -> &ContainerParser {
&self.reader
}
}
impl JxlImage {
#[inline]
pub fn set_cms(&mut self, cms: impl ColorManagementSystem + Send + Sync + 'static) {
self.ctx.set_cms(cms);
}
pub fn request_icc(&mut self, icc_profile: &[u8]) -> Result<()> {
self.ctx
.request_color_encoding(ColorEncodingWithProfile::with_icc(icc_profile)?);
Ok(())
}
pub fn request_color_encoding(&mut self, color_encoding: EnumColourEncoding) {
self.ctx
.request_color_encoding(ColorEncodingWithProfile::new(color_encoding))
}
}
impl JxlImage {
pub fn render_frame(&self, keyframe_index: usize) -> Result<Render> {
self.render_frame_cropped(keyframe_index)
}
pub fn render_frame_cropped(&self, keyframe_index: usize) -> Result<Render> {
let image = self.ctx.render_keyframe(keyframe_index)?;
let image_region = self
.ctx
.image_region()
.apply_orientation(&self.image_header);
let frame = self.ctx.keyframe(keyframe_index).unwrap();
let frame_header = frame.header();
let target_frame_region = image_region.translate(-frame_header.x0, -frame_header.y0);
let is_cmyk = self.ctx.requested_color_encoding().is_cmyk();
let result = Render {
keyframe_index,
name: frame_header.name.clone(),
duration: frame_header.duration,
orientation: self.image_header.metadata.orientation,
image,
extra_channels: self.convert_ec_info(),
target_frame_region,
color_bit_depth: self.image_header.metadata.bit_depth,
is_cmyk,
render_spot_color: self.render_spot_color,
};
Ok(result)
}
pub fn render_loading_frame(&mut self) -> Result<Render> {
self.render_loading_frame_cropped()
}
pub fn render_loading_frame_cropped(&mut self) -> Result<Render> {
let (frame, image) = self.ctx.render_loading_keyframe()?;
let frame_header = frame.header();
let name = frame_header.name.clone();
let duration = frame_header.duration;
let image_region = self
.ctx
.image_region()
.apply_orientation(&self.image_header);
let frame = self
.ctx
.frame(self.ctx.loaded_frames())
.or_else(|| self.ctx.frame(self.ctx.loaded_frames() - 1))
.unwrap();
let frame_header = frame.header();
let target_frame_region = image_region.translate(-frame_header.x0, -frame_header.y0);
let is_cmyk = self.ctx.requested_color_encoding().is_cmyk();
let result = Render {
keyframe_index: self.ctx.loaded_keyframes(),
name,
duration,
orientation: self.image_header.metadata.orientation,
image,
extra_channels: self.convert_ec_info(),
target_frame_region,
color_bit_depth: self.image_header.metadata.bit_depth,
is_cmyk,
render_spot_color: self.render_spot_color,
};
Ok(result)
}
pub fn current_image_region(&self) -> CropInfo {
let region = self.ctx.image_region();
region.into()
}
pub fn set_image_region(&mut self, region: CropInfo) -> &mut Self {
self.ctx.request_image_region(region.into());
self
}
}
impl JxlImage {
pub fn jpeg_reconstruction_status(&self) -> JpegReconstructionStatus {
match self.inner.aux_boxes.jbrd() {
AuxBoxData::Data(jbrd) => {
let header = jbrd.header();
let Ok(exif) = self.inner.aux_boxes.first_exif() else {
return JpegReconstructionStatus::Invalid;
};
let xml = self.inner.aux_boxes.first_xml();
if header.expected_icc_len() > 0 {
if !self.image_header.metadata.colour_encoding.want_icc() {
return JpegReconstructionStatus::Invalid;
} else if self.original_icc().is_none() {
return JpegReconstructionStatus::NeedMoreData;
}
}
if header.expected_exif_len() > 0 {
if exif.is_decoding() {
return JpegReconstructionStatus::NeedMoreData;
} else if exif.is_not_found() {
return JpegReconstructionStatus::Invalid;
}
}
if header.expected_xmp_len() > 0 {
if xml.is_decoding() {
return JpegReconstructionStatus::NeedMoreData;
} else if xml.is_not_found() {
return JpegReconstructionStatus::Invalid;
}
}
JpegReconstructionStatus::Available
}
AuxBoxData::Decoding => {
if self.num_loaded_frames() >= 2 {
return JpegReconstructionStatus::Invalid;
}
let Some(frame) = self.frame(0) else {
return JpegReconstructionStatus::NeedMoreData;
};
let frame_header = frame.header();
if frame_header.encoding != jxl_frame::header::Encoding::VarDct {
return JpegReconstructionStatus::Invalid;
}
if !frame_header.frame_type.is_normal_frame() {
return JpegReconstructionStatus::Invalid;
}
JpegReconstructionStatus::NeedMoreData
}
AuxBoxData::NotFound => JpegReconstructionStatus::Unavailable,
}
}
pub fn reconstruct_jpeg(&self, jpeg_output: impl std::io::Write) -> Result<()> {
let aux_boxes = &self.inner.aux_boxes;
let jbrd = match aux_boxes.jbrd() {
AuxBoxData::Data(jbrd) => jbrd,
AuxBoxData::Decoding => {
return Err(jxl_jbr::Error::ReconstructionDataIncomplete.into());
}
AuxBoxData::NotFound => {
return Err(jxl_jbr::Error::ReconstructionUnavailable.into());
}
};
if self.num_loaded_frames() == 0 {
return Err(jxl_jbr::Error::FrameDataIncomplete.into());
}
let jbrd_header = jbrd.header();
let expected_icc_len = jbrd_header.expected_icc_len();
let expected_exif_len = jbrd_header.expected_exif_len();
let expected_xmp_len = jbrd_header.expected_xmp_len();
let icc = if expected_icc_len > 0 {
self.original_icc().unwrap_or(&[])
} else {
&[]
};
let exif = if expected_exif_len > 0 {
let b = aux_boxes.first_exif()?;
b.map(|x| x.payload()).unwrap_or(&[])
} else {
&[]
};
let xmp = if expected_xmp_len > 0 {
aux_boxes.first_xml().unwrap_or(&[])
} else {
&[]
};
let frame = self.frame(0).unwrap();
jbrd.reconstruct(frame, icc, exif, xmp, &self.pool)?
.write(jpeg_output)?;
Ok(())
}
}
impl JxlImage {
fn convert_ec_info(&self) -> Vec<ExtraChannel> {
self.image_header
.metadata
.ec_info
.iter()
.map(|ec_info| ExtraChannel {
ty: ec_info.ty,
name: ec_info.name.clone(),
bit_depth: ec_info.bit_depth,
})
.collect()
}
}
#[derive(Debug)]
struct JxlImageInner {
end_of_image: bool,
buffer: Vec<u8>,
buffer_offset: usize,
frame_offsets: Vec<usize>,
aux_boxes: AuxBoxList,
}
impl JxlImageInner {
fn feed_bytes_inner(&mut self, ctx: &mut RenderContext, mut buf: &[u8]) -> Result<()> {
if buf.is_empty() {
return Ok(());
}
if self.end_of_image {
self.buffer.extend_from_slice(buf);
return Ok(());
}
if let Some(loading_frame) = ctx.current_loading_frame() {
debug_assert!(self.buffer.is_empty());
let len = buf.len();
buf = loading_frame.feed_bytes(buf)?;
let count = len - buf.len();
self.buffer_offset += count;
if loading_frame.is_loading_done() {
let is_last = loading_frame.header().is_last;
ctx.finalize_current_frame();
if is_last {
self.end_of_image = true;
self.buffer = buf.to_vec();
return Ok(());
}
}
if buf.is_empty() {
return Ok(());
}
}
self.buffer.extend_from_slice(buf);
let mut buf = &*self.buffer;
while !buf.is_empty() {
let mut bitstream = Bitstream::new(buf);
let frame = match ctx.load_frame_header(&mut bitstream) {
Ok(x) => x,
Err(e) if e.unexpected_eof() => {
self.buffer = buf.to_vec();
return Ok(());
}
Err(e) => {
return Err(e.into());
}
};
let frame_index = frame.index();
assert_eq!(self.frame_offsets.len(), frame_index);
self.frame_offsets.push(self.buffer_offset);
let read_bytes = bitstream.num_read_bits() / 8;
buf = &buf[read_bytes..];
let len = buf.len();
buf = frame.feed_bytes(buf)?;
let read_bytes = read_bytes + (len - buf.len());
self.buffer_offset += read_bytes;
if frame.is_loading_done() {
let is_last = frame.header().is_last;
ctx.finalize_current_frame();
if is_last {
self.end_of_image = true;
self.buffer = buf.to_vec();
return Ok(());
}
}
}
self.buffer.clear();
Ok(())
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PixelFormat {
Gray,
Graya,
Rgb,
Rgba,
Cmyk,
Cmyka,
}
impl PixelFormat {
#[inline]
pub fn channels(self) -> usize {
match self {
PixelFormat::Gray => 1,
PixelFormat::Graya => 2,
PixelFormat::Rgb => 3,
PixelFormat::Rgba => 4,
PixelFormat::Cmyk => 4,
PixelFormat::Cmyka => 5,
}
}
#[inline]
pub fn is_grayscale(self) -> bool {
matches!(self, Self::Gray | Self::Graya)
}
#[inline]
pub fn has_alpha(self) -> bool {
matches!(
self,
PixelFormat::Graya | PixelFormat::Rgba | PixelFormat::Cmyka
)
}
#[inline]
pub fn has_black(self) -> bool {
matches!(self, PixelFormat::Cmyk | PixelFormat::Cmyka)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum HdrType {
Pq,
Hlg,
}
#[derive(Debug)]
pub enum LoadResult {
Done(usize),
NeedMoreData,
NoMoreFrames,
}
#[derive(Debug)]
pub enum RenderResult {
Done(Render),
NeedMoreData,
NoMoreFrames,
}
#[derive(Debug)]
pub struct Render {
keyframe_index: usize,
name: Name,
duration: u32,
orientation: u32,
image: Arc<ImageWithRegion>,
extra_channels: Vec<ExtraChannel>,
target_frame_region: Region,
color_bit_depth: BitDepth,
is_cmyk: bool,
render_spot_color: bool,
}
impl Render {
#[inline]
pub fn keyframe_index(&self) -> usize {
self.keyframe_index
}
#[inline]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub fn duration(&self) -> u32 {
self.duration
}
#[inline]
pub fn orientation(&self) -> u32 {
self.orientation
}
pub fn stream(&self) -> ImageStream<'_> {
ImageStream::from_render(self, false)
}
pub fn stream_no_alpha(&self) -> ImageStream<'_> {
ImageStream::from_render(self, true)
}
#[inline]
pub fn image_all_channels(&self) -> FrameBuffer {
let fb: Vec<_> = self.image.buffer().iter().collect();
let mut bit_depth = vec![self.color_bit_depth; self.image.color_channels()];
for ec in &self.extra_channels {
bit_depth.push(ec.bit_depth);
}
let regions: Vec<_> = self
.image
.regions_and_shifts()
.iter()
.map(|(region, _)| *region)
.collect();
FrameBuffer::from_grids(
&fb,
&bit_depth,
®ions,
self.target_frame_region,
self.orientation,
)
}
pub fn image_planar(&self) -> Vec<FrameBuffer> {
let grids = self.image.buffer();
let bit_depth_it = std::iter::repeat_n(self.color_bit_depth, self.image.color_channels())
.chain(self.extra_channels.iter().map(|ec| ec.bit_depth));
let region_it = self
.image
.regions_and_shifts()
.iter()
.map(|(region, _)| *region);
bit_depth_it
.zip(region_it)
.zip(grids)
.map(|((bit_depth, region), x)| {
FrameBuffer::from_grids(
&[x],
&[bit_depth],
&[region],
self.target_frame_region,
self.orientation,
)
})
.collect()
}
#[inline]
pub fn color_channels(&self) -> &[ImageBuffer] {
let color_channels = self.image.color_channels();
&self.image.buffer()[..color_channels]
}
#[inline]
pub fn extra_channels(&self) -> (&[ExtraChannel], &[ImageBuffer]) {
let color_channels = self.image.color_channels();
(&self.extra_channels, &self.image.buffer()[color_channels..])
}
}
#[derive(Debug)]
pub struct ExtraChannel {
ty: ExtraChannelType,
name: Name,
bit_depth: BitDepth,
}
impl ExtraChannel {
#[inline]
pub fn ty(&self) -> ExtraChannelType {
self.ty
}
#[inline]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub fn is_black(&self) -> bool {
matches!(self.ty, ExtraChannelType::Black)
}
#[inline]
pub fn is_alpha(&self) -> bool {
matches!(self.ty, ExtraChannelType::Alpha { .. })
}
#[inline]
pub fn is_spot_colour(&self) -> bool {
matches!(self.ty, ExtraChannelType::SpotColour { .. })
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub struct CropInfo {
pub width: u32,
pub height: u32,
pub left: u32,
pub top: u32,
}
impl From<CropInfo> for jxl_render::Region {
fn from(value: CropInfo) -> Self {
Self {
left: value.left as i32,
top: value.top as i32,
width: value.width,
height: value.height,
}
}
}
impl From<jxl_render::Region> for CropInfo {
fn from(value: jxl_render::Region) -> Self {
Self {
left: value.left.max(0) as u32,
top: value.top.max(0) as u32,
width: value.width,
height: value.height,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum JpegReconstructionStatus {
Available,
Invalid,
Unavailable,
NeedMoreData,
}