diff --git a/.gitignore b/.gitignore index face367..5b97e83 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ sweep.timestamp build/ lcov.info .pijul +compile_commands.json +.cache/ \ No newline at end of file diff --git a/jpegxl-rs/src/decode.rs b/jpegxl-rs/src/decode.rs index 631b5ec..4b13a98 100644 --- a/jpegxl-rs/src/decode.rs +++ b/jpegxl-rs/src/decode.rs @@ -34,8 +34,12 @@ use crate::{ utils::check_valid_signature, }; +mod event; +pub use event::*; mod result; pub use result::*; +mod session; +pub use session::*; /// Basic information pub type BasicInfo = JxlBasicInfo; @@ -87,7 +91,7 @@ impl Default for PixelFormat { pub struct JxlDecoder<'pr, 'mm> { /// Opaque pointer to the underlying decoder #[builder(setter(skip))] - dec: *mut jpegxl_sys::decode::JxlDecoder, + ptr: *mut jpegxl_sys::decode::JxlDecoder, /// Override desired pixel format pub pixel_format: Option, @@ -161,7 +165,7 @@ impl<'pr, 'mm> JxlDecoderBuilder<'pr, 'mm> { /// /// # Errors /// Return [`DecodeError::CannotCreateDecoder`] if it fails to create the decoder. - pub fn build(&self) -> Result, DecodeError> { + pub fn build(&mut self) -> Result, DecodeError> { let mm = self.memory_manager.flatten(); let dec = unsafe { mm.map_or_else( @@ -175,7 +179,7 @@ impl<'pr, 'mm> JxlDecoderBuilder<'pr, 'mm> { } Ok(JxlDecoder { - dec, + ptr: dec, pixel_format: self.pixel_format.flatten(), skip_reorientation: self.skip_reorientation.flatten(), unpremul_alpha: self.unpremul_alpha.flatten(), @@ -217,14 +221,14 @@ impl<'pr, 'mm> JxlDecoder<'pr, 'mm> { let next_in = data.as_ptr(); let avail_in = std::mem::size_of_val(data) as _; - check_dec_status(unsafe { JxlDecoderSetInput(self.dec, next_in, avail_in) })?; - unsafe { JxlDecoderCloseInput(self.dec) }; + check_dec_status(unsafe { JxlDecoderSetInput(self.ptr, next_in, avail_in) })?; + unsafe { JxlDecoderCloseInput(self.ptr) }; let mut status; loop { use JxlDecoderStatus as s; - status = unsafe { JxlDecoderProcessInput(self.dec) }; + status = unsafe { JxlDecoderProcessInput(self.ptr) }; match status { s::NeedMoreInput | s::Error => return Err(DecodeError::GenericError), @@ -232,7 +236,7 @@ impl<'pr, 'mm> JxlDecoder<'pr, 'mm> { // Get the basic info s::BasicInfo => { check_dec_status(unsafe { - JxlDecoderGetBasicInfo(self.dec, basic_info.as_mut_ptr()) + JxlDecoderGetBasicInfo(self.ptr, basic_info.as_mut_ptr()) })?; if let Some(pr) = self.parallel_runner { @@ -252,7 +256,7 @@ impl<'pr, 'mm> JxlDecoder<'pr, 'mm> { let buf = unsafe { reconstruct_jpeg_buffer.as_mut().unwrap_unchecked() }; buf.resize(self.init_jpeg_buffer, 0); check_dec_status(unsafe { - JxlDecoderSetJPEGBuffer(self.dec, buf.as_mut_ptr(), buf.len()) + JxlDecoderSetJPEGBuffer(self.ptr, buf.as_mut_ptr(), buf.len()) })?; } @@ -261,11 +265,11 @@ impl<'pr, 'mm> JxlDecoder<'pr, 'mm> { // Safety: JpegNeedMoreOutput is only called when reconstruct_jpeg_buffer // is not None let buf = unsafe { reconstruct_jpeg_buffer.as_mut().unwrap_unchecked() }; - let need_to_write = unsafe { JxlDecoderReleaseJPEGBuffer(self.dec) }; + let need_to_write = unsafe { JxlDecoderReleaseJPEGBuffer(self.ptr) }; buf.resize(buf.len() + need_to_write, 0); check_dec_status(unsafe { - JxlDecoderSetJPEGBuffer(self.dec, buf.as_mut_ptr(), buf.len()) + JxlDecoderSetJPEGBuffer(self.ptr, buf.as_mut_ptr(), buf.len()) })?; } @@ -277,13 +281,13 @@ impl<'pr, 'mm> JxlDecoder<'pr, 'mm> { s::FullImage => continue, s::Success => { if let Some(buf) = reconstruct_jpeg_buffer.as_mut() { - let remaining = unsafe { JxlDecoderReleaseJPEGBuffer(self.dec) }; + let remaining = unsafe { JxlDecoderReleaseJPEGBuffer(self.ptr) }; buf.truncate(buf.len() - remaining); buf.shrink_to_fit(); } - unsafe { JxlDecoderReset(self.dec) }; + unsafe { JxlDecoderReset(self.ptr) }; let info = unsafe { basic_info.assume_init() }; return Ok(Metadata { @@ -312,7 +316,7 @@ impl<'pr, 'mm> JxlDecoder<'pr, 'mm> { fn setup_decoder(&self, icc: bool, reconstruct_jpeg: bool) -> Result<(), DecodeError> { if let Some(runner) = self.parallel_runner { check_dec_status(unsafe { - JxlDecoderSetParallelRunner(self.dec, runner.runner(), runner.as_opaque_ptr()) + JxlDecoderSetParallelRunner(self.ptr, runner.runner(), runner.as_opaque_ptr()) })?; } @@ -329,22 +333,22 @@ impl<'pr, 'mm> JxlDecoder<'pr, 'mm> { events }; - check_dec_status(unsafe { JxlDecoderSubscribeEvents(self.dec, events) })?; + check_dec_status(unsafe { JxlDecoderSubscribeEvents(self.ptr, events) })?; if let Some(val) = self.skip_reorientation { - check_dec_status(unsafe { JxlDecoderSetKeepOrientation(self.dec, val.into()) })?; + check_dec_status(unsafe { JxlDecoderSetKeepOrientation(self.ptr, val.into()) })?; } if let Some(val) = self.unpremul_alpha { - check_dec_status(unsafe { JxlDecoderSetUnpremultiplyAlpha(self.dec, val.into()) })?; + check_dec_status(unsafe { JxlDecoderSetUnpremultiplyAlpha(self.ptr, val.into()) })?; } if let Some(val) = self.render_spotcolors { - check_dec_status(unsafe { JxlDecoderSetRenderSpotcolors(self.dec, val.into()) })?; + check_dec_status(unsafe { JxlDecoderSetRenderSpotcolors(self.ptr, val.into()) })?; } if let Some(val) = self.coalescing { - check_dec_status(unsafe { JxlDecoderSetCoalescing(self.dec, val.into()) })?; + check_dec_status(unsafe { JxlDecoderSetCoalescing(self.ptr, val.into()) })?; } if let Some(val) = self.desired_intensity_target { - check_dec_status(unsafe { JxlDecoderSetDesiredIntensityTarget(self.dec, val) })?; + check_dec_status(unsafe { JxlDecoderSetDesiredIntensityTarget(self.ptr, val) })?; } Ok(()) @@ -353,13 +357,13 @@ impl<'pr, 'mm> JxlDecoder<'pr, 'mm> { fn get_icc_profile(&self, icc_profile: &mut Vec) -> Result<(), DecodeError> { let mut icc_size = 0; check_dec_status(unsafe { - JxlDecoderGetICCProfileSize(self.dec, JxlColorProfileTarget::Data, &mut icc_size) + JxlDecoderGetICCProfileSize(self.ptr, JxlColorProfileTarget::Data, &mut icc_size) })?; icc_profile.resize(icc_size, 0); check_dec_status(unsafe { JxlDecoderGetColorAsICCProfile( - self.dec, + self.ptr, JxlColorProfileTarget::Data, icc_profile.as_mut_ptr(), icc_size, @@ -401,18 +405,36 @@ impl<'pr, 'mm> JxlDecoder<'pr, 'mm> { let mut size = 0; check_dec_status(unsafe { - JxlDecoderImageOutBufferSize(self.dec, &pixel_format, &mut size) + JxlDecoderImageOutBufferSize(self.ptr, &pixel_format, &mut size) })?; pixels.resize(size, 0); check_dec_status(unsafe { - JxlDecoderSetImageOutBuffer(self.dec, &pixel_format, pixels.as_mut_ptr().cast(), size) + JxlDecoderSetImageOutBuffer(self.ptr, &pixel_format, pixels.as_mut_ptr().cast(), size) })?; unsafe { *format = pixel_format }; Ok(()) } + /// Start a new decoding session. + /// + /// Later event will overwrite the previous one if they are the same. + /// + /// # Arguments + /// + /// * `events` - The events to subscribe to during the session. + /// + /// # Errors + /// + /// Returns a [`DecodeError`] if the decoding session encounters an error. + pub fn session(&mut self, events: I) -> Result, DecodeError> + where + I: IntoIterator, + { + Session::new(self, events) + } + /// Decode a JPEG XL image /// /// # Errors @@ -496,7 +518,7 @@ impl<'pr, 'mm> JxlDecoder<'pr, 'mm> { impl<'prl, 'mm> Drop for JxlDecoder<'prl, 'mm> { fn drop(&mut self) { - unsafe { JxlDecoderDestroy(self.dec) }; + unsafe { JxlDecoderDestroy(self.ptr) }; } } diff --git a/jpegxl-rs/src/decode/session.rs b/jpegxl-rs/src/decode/session.rs new file mode 100644 index 0000000..6a961c3 --- /dev/null +++ b/jpegxl-rs/src/decode/session.rs @@ -0,0 +1,196 @@ +use std::{mem::MaybeUninit, sync::Arc}; + +use jpegxl_sys::{ + color_encoding::JxlColorEncoding, + decode::{ + JxlDecoderGetColorAsEncodedProfile, JxlDecoderGetColorAsICCProfile, + JxlDecoderGetICCProfileSize, JxlDecoderStatus, + }, +}; + +use super::{BasicInfo, ColorProfileTarget, Event, JxlDecoder}; +use crate::{decode::parse_events, errors::check_dec_status, DecodeError}; + +/// Represents the state of the session. +pub enum State { + /// Initial state of the session. + Init, + /// Basic information such as image dimensions and extra channels. + /// This event occurs max once per image. + BasicInfo(Arc), + /// ICC color profile . + IccProfile(Vec), + /// + ColorProfile(JxlColorEncoding), +} + +#[derive(Debug, Default)] +pub(crate) struct Config { + pub color_profile: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ColorEncodingConfig { + target: ColorProfileTarget, + icc_profile: bool, +} + +/// Represents a session for decoding JPEG XL images. +pub struct Session<'dec, 'pr, 'mm> { + dec: &'dec mut JxlDecoder<'pr, 'mm>, + basic_info: Option>, + config: Config, + state: State, +} + +impl<'dec, 'pr, 'mm> Session<'dec, 'pr, 'mm> { + pub(crate) fn new( + dec: &'dec mut JxlDecoder<'pr, 'mm>, + registered_events: I, + ) -> Result + where + I: IntoIterator, + { + use jpegxl_sys::decode::{JxlDecoderSetParallelRunner, JxlDecoderSubscribeEvents}; + + if let Some(runner) = dec.parallel_runner { + check_dec_status(unsafe { + JxlDecoderSetParallelRunner(dec.ptr, runner.runner(), runner.as_opaque_ptr()) + })?; + } + + let (flags, config) = parse_events(registered_events); + check_dec_status(unsafe { JxlDecoderSubscribeEvents(dec.ptr, flags) })?; + + macro_rules! set_value { + ( $( ($name:ident, $fn:ident) $(,)? )* ) => { + $( + if let Some(val) = dec.$name { + check_dec_status(unsafe { jpegxl_sys::decode::$fn(dec.ptr, val.into()) })?; + } + )* + }; + } + + set_value! { + (skip_reorientation, JxlDecoderSetKeepOrientation), + (unpremul_alpha, JxlDecoderSetUnpremultiplyAlpha), + (render_spotcolors, JxlDecoderSetRenderSpotcolors), + (coalescing, JxlDecoderSetCoalescing), + ( + desired_intensity_target, + JxlDecoderSetDesiredIntensityTarget + ) + } + + Ok(Self { + dec, + basic_info: None, + config, + state: State::Init, + }) + } + + fn step(&mut self, status: JxlDecoderStatus) -> Result { + use jpegxl_sys::decode::{JxlDecoderGetBasicInfo, JxlDecoderStatus as s}; + + match status { + s::Success => panic!("Unexpected success status"), + s::Error => Err(DecodeError::GenericError), + s::BasicInfo => { + let mut info = MaybeUninit::uninit(); + check_dec_status(unsafe { + JxlDecoderGetBasicInfo(self.dec.ptr, info.as_mut_ptr()) + })?; + + if let Some(pr) = self.dec.parallel_runner { + pr.callback_basic_info(unsafe { &*info.as_ptr() }); + } + + self.basic_info = Some(Arc::new(unsafe { info.assume_init() })); + Ok(State::BasicInfo(unsafe { + self.basic_info.as_ref().unwrap_unchecked().clone() + })) + } + s::ColorEncoding => { + let Some(config) = self.config.color_profile else { + return Err(DecodeError::InvalidUsage( + "Subscribe to color encoding event but without a color profile", + )); + }; + + if config.icc_profile { + let mut icc_size = 0; + let mut icc_profile = Vec::new(); + + check_dec_status(unsafe { + JxlDecoderGetICCProfileSize(self.dec.ptr, config.target, &mut icc_size) + })?; + icc_profile.resize(icc_size, 0); + + check_dec_status(unsafe { + JxlDecoderGetColorAsICCProfile( + self.dec.ptr, + config.target, + icc_profile.as_mut_ptr(), + icc_size, + ) + })?; + + Ok(State::IccProfile(icc_profile)) + } else { + let mut color_encoding = MaybeUninit::uninit(); + + check_dec_status(unsafe { + JxlDecoderGetColorAsEncodedProfile( + self.dec.ptr, + config.target, + color_encoding.as_mut_ptr(), + ) + })?; + Ok(State::ColorProfile(unsafe { color_encoding.assume_init() })) + } + } + _ => unimplemented!(), + } + } +} + +impl<'dec, 'pr, 'mm> Iterator for Session<'dec, 'pr, 'mm> { + type Item = Result; + + fn next(&mut self) -> Option { + use jpegxl_sys::decode::{JxlDecoderProcessInput, JxlDecoderStatus as s}; + + let status = unsafe { JxlDecoderProcessInput(self.dec.ptr) }; + + match status { + s::Success => None, + status => Some(self.step(status)), + } + } +} + +impl Drop for Session<'_, '_, '_> { + fn drop(&mut self) { + unsafe { jpegxl_sys::decode::JxlDecoderReset(self.dec.ptr) } + } +} + +#[cfg(test)] +mod tests { + use testresult::TestResult; + + use crate::decoder_builder; + + use super::*; + + #[test] + fn test_session() -> TestResult { + let mut decoder = decoder_builder().build()?; + let session = Session::new(&mut decoder, [Event::BasicInfo])?; + drop(session); + + Ok(()) + } +}