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 cd3b798 commit c36037a
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 67 deletions.
27 changes: 22 additions & 5 deletions jpegxl-rs/src/decode/event.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
use std::ffi::c_int;

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

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,

Check warning on line 34 in jpegxl-rs/src/decode/event.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/event.rs#L32-L34

Added lines #L32 - L34 were not covered by tests
}
}
}
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

Check warning on line 49 in jpegxl-rs/src/decode/event.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/event.rs#L47-L49

Added lines #L47 - L49 were not covered by tests
}
_ => config,
};
(flag, config)
Expand Down
218 changes: 156 additions & 62 deletions jpegxl-rs/src/decode/session.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,40 @@
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,
},
color::color_encoding::JxlColorEncoding, common::types::JxlPixelFormat, decode as d,
};

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 {

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

View workflow job for this annotation

GitHub Actions / clippy

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

error: field `frame` is never read --> jpegxl-rs/src/decode/session.rs:33:9 | 30 | pub(crate) struct Config { | ------ field in this struct ... 33 | pub frame: Option<usize>, | ^^^^^ | = note: `Config` has derived impls for the traits `Default` and `Debug`, but these are intentionally ignored during dead code analysis = note: `-D dead-code` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(dead_code)]`
Raw output
jpegxl-rs/src/decode/session.rs:30:19:e:error: field `frame` is never read
  --> jpegxl-rs/src/decode/session.rs:33:9
   |
30 | pub(crate) struct Config {
   |                   ------ field in this struct
...
33 |     pub frame: Option<usize>,
   |         ^^^^^
   |
   = note: `Config` has derived impls for the traits `Default` and `Debug`, but these are intentionally ignored during dead code analysis
   = note: `-D dead-code` implied by `-D warnings`
   = help: to override `-D warnings` add `#[allow(dead_code)]`


__END__
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 +45,7 @@ pub struct ColorEncodingConfig {
pub struct Session<'dec, 'pr, 'mm> {

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

View workflow job for this annotation

GitHub Actions / clippy

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

error: field `state` is never read --> jpegxl-rs/src/decode/session.rs:50:5 | 45 | pub struct Session<'dec, 'pr, 'mm> { | ------- field in this struct ... 50 | state: State, | ^^^^^
Raw output
jpegxl-rs/src/decode/session.rs:45:12:e:error: field `state` is never read
  --> jpegxl-rs/src/decode/session.rs:50:5
   |
45 | pub struct Session<'dec, 'pr, 'mm> {
   |            ------- field in this struct
...
50 |     state: State,
   |     ^^^^^


__END__
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 +93,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> {

Check warning on line 102 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L102

Added line #L102 was not covered by tests
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(),

Check warning on line 111 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L105-L111

Added lines #L105 - L111 were not covered by tests
s::JPEGReconstruction => {
let Some(size) = self.config.jpeg_reconstruction else {
return Err(DecodeError::InternalError(
"Subscribe to JPEG reconstruction event but without a config!",
));

Check warning on line 116 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L113-L116

Added lines #L113 - L116 were not covered by tests
};

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(),
)
})?;

Check warning on line 127 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L119-L127

Added lines #L119 - L127 were not covered by tests

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)

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

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L129

Added line #L129 was not covered by tests
}
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 => {
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(),
)
})?;

Check warning on line 143 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L132-L143

Added lines #L132 - L143 were not covered by tests

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)

Check warning on line 145 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L145

Added line #L145 was not covered by tests
}
_ => unimplemented!(),

Check warning on line 147 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L147

Added line #L147 was not covered by tests
}
}

Check warning on line 149 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L149

Added line #L149 was not covered by tests

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()) })?;

Check warning on line 153 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L151-L153

Added lines #L151 - L153 were not covered by tests

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

Check warning on line 157 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L155-L157

Added lines #L155 - L157 were not covered by tests

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

Check warning on line 163 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L160-L163

Added lines #L160 - L163 were not covered by tests
}
}

Check warning on line 165 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L165

Added line #L165 was not covered by tests

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!",
));

Check warning on line 171 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L167-L171

Added lines #L167 - L171 were not covered by tests
};

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,
)
})?;

Check warning on line 190 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L174-L190

Added lines #L174 - L190 were not covered by tests

Ok(State::IccProfile(icc_profile))

Check warning on line 192 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L192

Added line #L192 was not covered by tests
} 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()
})))

Check warning on line 205 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L194-L205

Added lines #L194 - L205 were not covered by tests
}
}

Check warning on line 207 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L207

Added line #L207 was not covered by tests

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!",
));

Check warning on line 213 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L209-L213

Added lines #L209 - L213 were not covered by tests
};

let mut size = 0;

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

Check warning on line 220 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L216-L220

Added lines #L216 - L220 were not covered by tests

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

Check warning on line 230 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L222-L230

Added lines #L222 - L230 were not covered by tests

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

Check warning on line 233 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L232-L233

Added lines #L232 - L233 were not covered by tests

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 246 in jpegxl-rs/src/decode/session.rs

View workflow job for this annotation

GitHub Actions / clippy

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

error: unused variable: `name` --> jpegxl-rs/src/decode/session.rs:246:13 | 246 | 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:246:13:e:error: unused variable: `name`
   --> jpegxl-rs/src/decode/session.rs:246:13
    |
246 |         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"))?;

Check warning on line 247 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L235-L247

Added lines #L235 - L247 were not covered by tests

Ok(State::Frame)
}

Check warning on line 250 in jpegxl-rs/src/decode/session.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/decode/session.rs#L249-L250

Added lines #L249 - L250 were not covered by tests
}

impl<'dec, 'pr, 'mm> Iterator for Session<'dec, 'pr, 'mm> {
Expand Down

0 comments on commit c36037a

Please sign in to comment.