#![allow(clippy::redundant_static_lifetimes)]
#![allow(clippy::too_many_arguments)]
pub mod bindings;
pub use bindings::*;
macro_rules! trait_impl {
($x:ty, [$($struct_:ident ),*]) => {
$(
impl $x for $struct_ {}
)*
};
}
trait_impl!(NewUninit, [JxlBasicInfo, JxlPixelFormat, JxlColorEncoding]);
pub trait NewUninit {
#[inline]
#[must_use]
fn new_uninit() -> std::mem::MaybeUninit<Self>
where
Self: std::marker::Sized,
{
std::mem::MaybeUninit::<Self>::uninit()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::thread_runner::*;
use std::ptr;
use image::io::Reader as ImageReader;
use image::ImageError;
macro_rules! jxl_dec_assert {
($val:expr, $desc:expr) => {
if $val != JxlDecoderStatus::Success as _ {
panic!("Decoder error by: {:#?}, in {}", $val, $desc)
}
};
}
macro_rules! jxl_enc_assert {
($val:expr, $desc:expr) => {
if $val != JxlEncoderStatus::Success as _ {
panic!("Encoder error by: {:#?}, in {}", $val, $desc)
}
};
}
#[test]
fn test_bindings_version() {
unsafe {
assert_eq!(JxlDecoderVersion(), 3007);
assert_eq!(JxlEncoderVersion(), 3007);
}
}
unsafe fn decode(decoder: *mut JxlDecoder, sample: &[u8]) {
let mut status;
status = JxlDecoderSubscribeEvents(
decoder,
jxl_dec_events!(JxlDecoderStatus::BasicInfo, JxlDecoderStatus::FullImage),
);
jxl_dec_assert!(status, "Subscribe Events");
let signature = JxlSignatureCheck(sample.as_ptr(), 2);
assert_eq!(signature, JxlSignature::Codestream, "Signature");
let next_in = sample.as_ptr();
let avail_in = sample.len();
let pixel_format = JxlPixelFormat {
num_channels: 3,
data_type: JxlDataType::Uint8,
endianness: JxlEndianness::Native,
align: 0,
};
let mut basic_info = JxlBasicInfo::new_uninit().assume_init();
let mut buffer: Vec<f32> = Vec::new();
let mut xsize = 0;
let mut ysize = 0;
status = JxlDecoderSetInput(decoder, next_in, avail_in);
jxl_dec_assert!(status, "Set input");
use JxlDecoderStatus::*;
loop {
status = JxlDecoderProcessInput(decoder);
match status {
Error => panic!("Decoder error!"),
NeedMoreInput => {
panic!("Error, already provided all input")
}
BasicInfo => {
status = JxlDecoderGetBasicInfo(decoder, &mut basic_info);
jxl_dec_assert!(status, "BasicInfo");
xsize = basic_info.xsize;
ysize = basic_info.ysize;
assert_eq!(basic_info.bits_per_sample, 8, "Bits per sample");
assert_eq!(basic_info.xsize, 2122, "Width");
assert_eq!(basic_info.ysize, 1433, "Height");
}
NeedImageOutBuffer => {
let mut size = 0;
status = JxlDecoderImageOutBufferSize(decoder, &pixel_format, &mut size);
jxl_dec_assert!(status, "BufferSize");
buffer.resize(size as usize, 0f32);
status = JxlDecoderSetImageOutBuffer(
decoder,
&pixel_format,
buffer.as_mut_ptr() as *mut std::ffi::c_void,
size,
);
jxl_dec_assert!(status, "SetBuffer");
}
FullImage => continue,
Success => {
assert_eq!(buffer.len(), (xsize * ysize * 3) as usize);
return;
}
_ => panic!("Unknown decoder status: {:#?}", status),
}
}
}
#[test]
fn test_bindings_decoding() {
unsafe {
let dec = JxlDecoderCreate(ptr::null());
assert!(!dec.is_null());
let sample = std::fs::read("test/sample.jxl").unwrap();
decode(dec, &sample);
JxlDecoderDestroy(dec);
}
}
#[test]
fn test_bindings_thread_pool() {
unsafe {
let runner = JxlThreadParallelRunnerCreate(
std::ptr::null(),
JxlThreadParallelRunnerDefaultNumWorkerThreads(),
);
let dec = JxlDecoderCreate(ptr::null());
assert!(!dec.is_null());
let status = JxlDecoderSetParallelRunner(dec, Some(JxlThreadParallelRunner), runner);
jxl_dec_assert!(status, "Set Parallel Runner");
let sample = std::fs::read("test/sample.jxl").unwrap();
decode(dec, &sample);
JxlDecoderDestroy(dec);
JxlThreadParallelRunnerDestroy(runner);
}
}
fn encode(pixels: &[u8], xsize: u32, ysize: u32) -> Vec<u8> {
unsafe {
let enc = JxlEncoderCreate(std::ptr::null());
let runner = JxlThreadParallelRunnerCreate(
std::ptr::null(),
JxlThreadParallelRunnerDefaultNumWorkerThreads(),
);
let mut status =
JxlEncoderSetParallelRunner(enc, Some(JxlThreadParallelRunner), runner);
jxl_enc_assert!(status, "Set Parallel Runner");
let mut basic_info = JxlBasicInfo::new_uninit().assume_init();
basic_info.xsize = xsize;
basic_info.ysize = ysize;
basic_info.bits_per_sample = 8;
basic_info.exponent_bits_per_sample = 0;
basic_info.alpha_exponent_bits = 0;
basic_info.alpha_bits = 0;
basic_info.uses_original_profile = false as _;
status = JxlEncoderSetBasicInfo(enc, &basic_info);
jxl_enc_assert!(status, "Set Basic Info");
let pixel_format = JxlPixelFormat {
num_channels: 3,
data_type: JxlDataType::Uint8,
endianness: JxlEndianness::Native,
align: 0,
};
let mut color_encoding = JxlColorEncoding::new_uninit().assume_init();
JxlColorEncodingSetToSRGB(&mut color_encoding, false);
status = JxlEncoderSetColorEncoding(enc, &color_encoding);
jxl_enc_assert!(status, "Set Color Encoding");
status = JxlEncoderAddImageFrame(
JxlEncoderOptionsCreate(enc, std::ptr::null()),
&pixel_format,
pixels.as_ptr() as *mut std::ffi::c_void,
pixels.len(),
);
jxl_enc_assert!(status, "Add Image Frame");
const CHUNK_SIZE: usize = 1024 * 512;
let mut buffer = vec![0u8; CHUNK_SIZE];
let mut next_out = buffer.as_mut_ptr();
let mut avail_out = CHUNK_SIZE;
loop {
status =
JxlEncoderProcessOutput(enc, &mut next_out as *mut *mut u8, &mut avail_out);
if status != JxlEncoderStatus::NeedMoreOutput {
break;
}
let offset = next_out as usize - buffer.as_ptr() as usize;
buffer.resize(buffer.len() * 2, 0);
next_out = buffer.as_mut_ptr().add(offset);
avail_out = buffer.len() - offset;
}
buffer.truncate(next_out as usize - buffer.as_ptr() as usize);
jxl_enc_assert!(status, "Encoding");
JxlEncoderDestroy(enc);
JxlThreadParallelRunnerDestroy(runner);
buffer
}
}
#[test]
fn test_bindings_encoding() {
|| -> Result<(), ImageError> {
let img = ImageReader::open("test/sample.png")?.decode()?;
let image_buffer = img.into_rgb8();
let output = encode(
image_buffer.as_raw(),
image_buffer.width(),
image_buffer.height(),
);
unsafe {
let runner = JxlThreadParallelRunnerCreate(
std::ptr::null(),
JxlThreadParallelRunnerDefaultNumWorkerThreads(),
);
let dec = JxlDecoderCreate(ptr::null());
assert!(!dec.is_null());
let status =
JxlDecoderSetParallelRunner(dec, Some(JxlThreadParallelRunner), runner);
jxl_dec_assert!(status, "Set Parallel Runner");
decode(dec, &output);
JxlDecoderDestroy(dec);
JxlThreadParallelRunnerDestroy(runner);
}
Ok(())
}()
.unwrap();
}
}