Skip to content

Commit

Permalink
feat: Implement preview and jpeg reconstruction events
Browse files Browse the repository at this point in the history
  • Loading branch information
inflation committed Oct 1, 2024
1 parent f15aaa2 commit 42c7313
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 75 deletions.
27 changes: 22 additions & 5 deletions jpegxl-rs/src/decode/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,36 @@ use std::ffi::c_int;

/// Target of the color profile.
pub use jpegxl_sys::decode::JxlColorProfileTarget as ColorProfileTarget;
pub use jpegxl_sys::types::JxlPixelFormat;

Check failure on line 5 in jpegxl-rs/src/decode/event.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] jpegxl-rs/src/decode/event.rs#L5

error[E0432]: unresolved import `jpegxl_sys::types` --> jpegxl-rs/src/decode/event.rs:5:21 | 5 | pub use jpegxl_sys::types::JxlPixelFormat; | ^^^^^ could not find `types` in `jpegxl_sys`
Raw output
jpegxl-rs/src/decode/event.rs:5:21:e:error[E0432]: unresolved import `jpegxl_sys::types`
 --> jpegxl-rs/src/decode/event.rs:5:21
  |
5 | pub use jpegxl_sys::types::JxlPixelFormat;
  |                     ^^^^^ could not find `types` in `jpegxl_sys`


__END__

use super::{ColorEncodingConfig, Config};

/// Events that can be subscribed to.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Event {
/// Basic information.
BasicInfo,
/// Color encoding.
ColorEncoding(ColorEncodingConfig),
/// Preview image.
PreviewImage {
/// Pixel format.
pixel_format: JxlPixelFormat,
},
/// JPEG reconstruction.
JpegReconstruction {
/// Initial buffer size. Increase it to reduce the number of reallocations.
init_buffer_size: usize,
},
}

impl From<Event> for c_int {
fn from(value: Event) -> Self {
match value {
Event::BasicInfo => 0x40,
Event::ColorEncoding { .. } => 0x100,
Event::ColorEncoding(_) => 0x100,
Event::PreviewImage { .. } => 0x200,
Event::JpegReconstruction { .. } => 0x2000,
}
}
}
Expand All @@ -25,12 +41,13 @@ where
I: IntoIterator<Item = Event>,
{
iter.into_iter()
.fold((0, Config::default()), |(flag, config), x| {
.fold((0, Config::default()), |(flag, mut config), x| {
let flag = flag | c_int::from(x);
let config = match x {
Event::ColorEncoding(val) => Config {
color_profile: Some(val),
},
Event::ColorEncoding(val) => {
config.color_profile = Some(val);
config
}
_ => config,
};
(flag, config)
Expand Down
220 changes: 156 additions & 64 deletions jpegxl-rs/src/decode/session.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
use std::{mem::MaybeUninit, sync::Arc};
use std::{ffi::CString, mem::MaybeUninit, sync::Arc};

use jpegxl_sys::{
color_encoding::JxlColorEncoding,
decode::{
JxlDecoderGetColorAsEncodedProfile, JxlDecoderGetColorAsICCProfile,
JxlDecoderGetICCProfileSize, JxlDecoderStatus,
},
};
use jpegxl_sys::{color_encoding::JxlColorEncoding, decode as d, types::JxlPixelFormat};

Check failure on line 3 in jpegxl-rs/src/decode/session.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] jpegxl-rs/src/decode/session.rs#L3

error[E0432]: unresolved imports `jpegxl_sys::color_encoding`, `jpegxl_sys::types` --> jpegxl-rs/src/decode/session.rs:3:18 | 3 | use jpegxl_sys::{color_encoding::JxlColorEncoding, decode as d, types::JxlPixelFormat}; | ^^^^^^^^^^^^^^ ^^^^^ could not find `types` in `jpegxl_sys` | | | could not find `color_encoding` in `jpegxl_sys`
Raw output
jpegxl-rs/src/decode/session.rs:3:18:e:error[E0432]: unresolved imports `jpegxl_sys::color_encoding`, `jpegxl_sys::types`
 --> jpegxl-rs/src/decode/session.rs:3:18
  |
3 | use jpegxl_sys::{color_encoding::JxlColorEncoding, decode as d, types::JxlPixelFormat};
  |                  ^^^^^^^^^^^^^^                                 ^^^^^ could not find `types` in `jpegxl_sys`
  |                  |
  |                  could not find `color_encoding` in `jpegxl_sys`


__END__

use super::{BasicInfo, ColorProfileTarget, Event, JxlDecoder};
use super::{BasicInfo, ColorProfileTarget, Event, JxlDecoder, Pixels};
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,
/// Waiting for the next event.
Continue,
/// Basic information such as image dimensions and extra channels.
/// This event occurs max once per image.
BasicInfo(Arc<BasicInfo>),
/// ICC color profile .
IccProfile(Vec<u8>),
///
ColorProfile(JxlColorEncoding),
/// Color profile.
ColorProfile(Box<JxlColorEncoding>),
/// Preview image. Dimensions can be accessed from [`BasicInfo::preview`]
PreviewImage(Pixels),
/// Begining of a frame
Frame,
/// JPEG reconstruction.
JpegReconstruction(Vec<u8>),
}

#[derive(Debug, Default)]
pub(crate) struct Config {
pub color_profile: Option<ColorEncodingConfig>,
pub preview: Option<JxlPixelFormat>,
pub frame: Option<usize>,
pub jpeg_reconstruction: Option<usize>,
}

/// Configuration for color encoding.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColorEncodingConfig {
target: ColorProfileTarget,
Expand All @@ -39,6 +43,7 @@ pub struct ColorEncodingConfig {
pub struct Session<'dec, 'pr, 'mm> {
dec: &'dec mut JxlDecoder<'pr, 'mm>,
basic_info: Option<Arc<BasicInfo>>,
jpeg_buffer: Vec<u8>,
config: Config,
state: State,
}
Expand Down Expand Up @@ -86,74 +91,161 @@ impl<'dec, 'pr, 'mm> Session<'dec, 'pr, 'mm> {
Ok(Self {
dec,
basic_info: None,
jpeg_buffer: Vec::new(),
config,
state: State::Init,
state: State::Continue,
})
}

fn step(&mut self, status: JxlDecoderStatus) -> Result<State, DecodeError> {
use jpegxl_sys::decode::{JxlDecoderGetBasicInfo, JxlDecoderStatus as s};
fn step(&mut self, status: d::JxlDecoderStatus) -> Result<State, DecodeError> {
use jpegxl_sys::decode::JxlDecoderStatus as s;

match status {
s::Success => panic!("Unexpected success status"),
s::Error => Err(DecodeError::GenericError),
s::BasicInfo => {
let mut info = MaybeUninit::uninit();
s::BasicInfo => self.get_basic_info(),
s::ColorEncoding => self.get_color_profile(),
s::PreviewImage => self.get_preview_image(),
s::Frame => self.get_frame(),
s::JpegReconstruction => {

Check failure on line 110 in jpegxl-rs/src/decode/session.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] jpegxl-rs/src/decode/session.rs#L110

error[E0599]: no variant or associated item named `JpegReconstruction` found for enum `jpegxl_sys::decode::JxlDecoderStatus` in the current scope --> jpegxl-rs/src/decode/session.rs:110:16 | 110 | s::JpegReconstruction => { | ^^^^^^^^^^^^^^^^^^ variant or associated item not found in `JxlDecoderStatus` | help: there is a variant with a similar name | 110 | s::JPEGReconstruction => { | ~~~~~~~~~~~~~~~~~~
Raw output
jpegxl-rs/src/decode/session.rs:110:16:e:error[E0599]: no variant or associated item named `JpegReconstruction` found for enum `jpegxl_sys::decode::JxlDecoderStatus` in the current scope
   --> jpegxl-rs/src/decode/session.rs:110:16
    |
110 |             s::JpegReconstruction => {
    |                ^^^^^^^^^^^^^^^^^^ variant or associated item not found in `JxlDecoderStatus`
    |
help: there is a variant with a similar name
    |
110 |             s::JPEGReconstruction => {
    |                ~~~~~~~~~~~~~~~~~~


__END__
let Some(size) = self.config.jpeg_reconstruction else {
return Err(DecodeError::InternalError(
"Subscribe to JPEG reconstruction event but without a config!",
));
};

self.jpeg_buffer.resize(size, 0);

check_dec_status(unsafe {
JxlDecoderGetBasicInfo(self.dec.ptr, info.as_mut_ptr())
d::JxlDecoderSetJPEGBuffer(
self.dec.ptr,
self.jpeg_buffer.as_mut_ptr(),
self.jpeg_buffer.len(),
)
})?;

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()
}))
Ok(State::Continue)
}
s::ColorEncoding => {
let Some(config) = self.config.color_profile else {
return Err(DecodeError::InvalidUsage(
"Subscribe to color encoding event but without a color profile",
));
};
s::JpegNeedMoreOutput => {

Check failure on line 129 in jpegxl-rs/src/decode/session.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] jpegxl-rs/src/decode/session.rs#L129

error[E0599]: no variant or associated item named `JpegNeedMoreOutput` found for enum `jpegxl_sys::decode::JxlDecoderStatus` in the current scope --> jpegxl-rs/src/decode/session.rs:129:16 | 129 | s::JpegNeedMoreOutput => { | ^^^^^^^^^^^^^^^^^^ variant or associated item not found in `JxlDecoderStatus` | help: there is a variant with a similar name | 129 | s::JPEGNeedMoreOutput => { | ~~~~~~~~~~~~~~~~~~
Raw output
jpegxl-rs/src/decode/session.rs:129:16:e:error[E0599]: no variant or associated item named `JpegNeedMoreOutput` found for enum `jpegxl_sys::decode::JxlDecoderStatus` in the current scope
   --> jpegxl-rs/src/decode/session.rs:129:16
    |
129 |             s::JpegNeedMoreOutput => {
    |                ^^^^^^^^^^^^^^^^^^ variant or associated item not found in `JxlDecoderStatus`
    |
help: there is a variant with a similar name
    |
129 |             s::JPEGNeedMoreOutput => {
    |                ~~~~~~~~~~~~~~~~~~


__END__
let remaining = unsafe { d::JxlDecoderReleaseJPEGBuffer(self.dec.ptr) };

self.jpeg_buffer
.resize(self.jpeg_buffer.len() + remaining, 0);

check_dec_status(unsafe {
d::JxlDecoderSetJPEGBuffer(
self.dec.ptr,
self.jpeg_buffer.as_mut_ptr(),
self.jpeg_buffer.len(),
)
})?;

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() }))
}
Ok(State::Continue)
}
_ => unimplemented!(),
}
}

fn get_basic_info(&mut self) -> Result<State, DecodeError> {
let mut info = MaybeUninit::uninit();
check_dec_status(unsafe { d::JxlDecoderGetBasicInfo(self.dec.ptr, info.as_mut_ptr()) })?;

if let Some(pr) = self.dec.parallel_runner {
pr.callback_basic_info(unsafe { &*info.as_ptr() });
}

unsafe {
self.basic_info = Some(Arc::new(info.assume_init()));
Ok(State::BasicInfo(
self.basic_info.as_ref().unwrap_unchecked().clone(),
))
}
}

fn get_color_profile(&mut self) -> Result<State, DecodeError> {
let Some(config) = self.config.color_profile else {
return Err(DecodeError::InternalError(
"Subscribe to color encoding event but without a color profile config!",
));
};

if config.icc_profile {
let mut icc_size = 0;
let mut icc_profile = Vec::new();

check_dec_status(unsafe {
d::JxlDecoderGetICCProfileSize(self.dec.ptr, config.target, &mut icc_size)
})?;
icc_profile.resize(icc_size, 0);

check_dec_status(unsafe {
d::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 {
d::JxlDecoderGetColorAsEncodedProfile(
self.dec.ptr,
config.target,
color_encoding.as_mut_ptr(),
)
})?;
Ok(State::ColorProfile(Box::new(unsafe {
color_encoding.assume_init()
})))
}
}

fn get_preview_image(&mut self) -> Result<State, DecodeError> {
let Some(pixel_format) = self.config.preview else {
return Err(DecodeError::InternalError(
"Subscribe to preview image event but without a pixel format!",
));
};

let mut size = 0;

check_dec_status(unsafe {
d::JxlDecoderPreviewOutBufferSize(self.dec.ptr, &pixel_format, &mut size)
})?;

let mut buffer = vec![0; size];
check_dec_status(unsafe {
d::JxlDecoderSetPreviewOutBuffer(
self.dec.ptr,
&pixel_format,
buffer.as_mut_ptr().cast(),
buffer.len(),
)
})?;

Ok(State::PreviewImage(Pixels::new(buffer, &pixel_format)))
}

fn get_frame(&mut self) -> Result<State, DecodeError> {
let mut header = MaybeUninit::uninit();
check_dec_status(unsafe {
d::JxlDecoderGetFrameHeader(self.dec.ptr, header.as_mut_ptr())
})?;
let header = unsafe { header.assume_init() };

let mut buffer = vec![0; header.name_length as usize + 1];
check_dec_status(unsafe {
d::JxlDecoderGetFrameName(self.dec.ptr, buffer.as_mut_ptr().cast(), buffer.len())
})?;
let name = CString::from_vec_with_nul(buffer)

Check failure on line 244 in jpegxl-rs/src/decode/session.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] jpegxl-rs/src/decode/session.rs#L244

error: unused variable: `name` --> jpegxl-rs/src/decode/session.rs:244:13 | 244 | let name = CString::from_vec_with_nul(buffer) | ^^^^ help: if this is intentional, prefix it with an underscore: `_name` | = note: `-D unused-variables` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(unused_variables)]`
Raw output
jpegxl-rs/src/decode/session.rs:244:13:e:error: unused variable: `name`
   --> jpegxl-rs/src/decode/session.rs:244:13
    |
244 |         let name = CString::from_vec_with_nul(buffer)
    |             ^^^^ help: if this is intentional, prefix it with an underscore: `_name`
    |
    = note: `-D unused-variables` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(unused_variables)]`


__END__
.map_err(|_| DecodeError::InternalError("Invalid frame name"))?;

Ok(State::Frame)
}
}

impl<'dec, 'pr, 'mm> Iterator for Session<'dec, 'pr, 'mm> {
Expand Down
6 changes: 0 additions & 6 deletions jpegxl-rs/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,9 @@ pub enum DecodeError {
/// Unsupported Pixel bit width
#[error("Unsupported Pixel bit width: {0}")]
UnsupportedBitWidth(u32),
<<<<<<< HEAD
/// Internal error, usually invalid usages of the `libjxl` library
#[error("Internal error, please file an issus: {0}")]
InternalError(&'static str),
||||||| parent of b7164a7 (refactor: Update error messages for more information on how to debug issues)
=======
#[error("Invalid usage of the library, please file an issus: {0}")]
InvalidUsage(&'static str),
>>>>>>> b7164a7 (refactor: Update error messages for more information on how to debug issues)
/// Unknown status
#[error("Unknown status: `{0:?}`")]
UnknownStatus(JxlDecoderStatus),
Expand Down
Loading

0 comments on commit 42c7313

Please sign in to comment.