use std::ptr;
use libjxl_sys::*;
use crate::error::{Error, Result};
use super::types::EncodeConfig;
pub(crate) struct Encoder {
ptr: *mut JxlEncoder,
}
impl Encoder {
pub fn new() -> Result<Self> {
let ptr = unsafe { JxlEncoderCreate(ptr::null()) };
if ptr.is_null() {
return Err(Error::Encode("failed to create JXL encoder".into()));
}
Ok(Self { ptr })
}
pub fn encode(
&mut self,
pixels: &[u8],
width: u32,
height: u32,
num_channels: u32,
config: &EncodeConfig,
) -> Result<Vec<u8>> {
debug_assert!(num_channels == 3 || num_channels == 4);
unsafe { JxlEncoderReset(self.ptr) };
self.set_basic_info(width, height, num_channels, config)?;
self.set_color_encoding()?;
let frame_settings = unsafe { JxlEncoderFrameSettingsCreate(self.ptr, ptr::null()) };
if frame_settings.is_null() {
return Err(Error::Encode("failed to create frame settings".into()));
}
self.configure_frame(frame_settings, config)?;
self.add_frame(frame_settings, pixels, width, height, num_channels)?;
unsafe { JxlEncoderCloseInput(self.ptr) };
self.process_output()
}
fn set_basic_info(
&self,
width: u32,
height: u32,
num_channels: u32,
config: &EncodeConfig,
) -> Result<()> {
let has_alpha = num_channels == 4;
unsafe {
let mut info: JxlBasicInfo = std::mem::zeroed();
info.xsize = width;
info.ysize = height;
info.bits_per_sample = 8;
info.exponent_bits_per_sample = 0;
info.num_color_channels = 3;
info.num_extra_channels = if has_alpha { 1 } else { 0 };
info.alpha_bits = if has_alpha { 8 } else { 0 };
info.alpha_exponent_bits = 0;
info.orientation = JxlOrientation_JXL_ORIENT_IDENTITY;
info.uses_original_profile = if config.lossless { 1 } else { 0 };
check_status(JxlEncoderSetBasicInfo(self.ptr, &info), "set basic info")
}
}
fn set_color_encoding(&self) -> Result<()> {
unsafe {
let mut color: JxlColorEncoding = std::mem::zeroed();
JxlColorEncodingSetToSRGB(&mut color, 0); check_status(
JxlEncoderSetColorEncoding(self.ptr, &color),
"set color encoding",
)
}
}
fn configure_frame(
&self,
settings: *mut JxlEncoderFrameSettings,
config: &EncodeConfig,
) -> Result<()> {
if config.lossless {
unsafe {
check_status(JxlEncoderSetFrameLossless(settings, 1), "set lossless")?;
}
}
unsafe {
check_status(
JxlEncoderSetFrameDistance(settings, config.distance),
"set distance",
)
}
}
fn add_frame(
&self,
settings: *mut JxlEncoderFrameSettings,
pixels: &[u8],
width: u32,
height: u32,
num_channels: u32,
) -> Result<()> {
let format = JxlPixelFormat {
num_channels,
data_type: JxlDataType_JXL_TYPE_UINT8,
endianness: JxlEndianness_JXL_NATIVE_ENDIAN,
align: 0,
};
let expected = (width as usize) * (height as usize) * num_channels as usize;
debug_assert_eq!(pixels.len(), expected);
unsafe {
check_status(
JxlEncoderAddImageFrame(settings, &format, pixels.as_ptr().cast(), pixels.len()),
"add image frame",
)
}
}
fn process_output(&self) -> Result<Vec<u8>> {
let mut buffer = vec![0u8; 64 * 1024]; let mut all_output = Vec::new();
loop {
let mut next_out = buffer.as_mut_ptr();
let mut avail_out = buffer.len();
let status =
unsafe { JxlEncoderProcessOutput(self.ptr, &mut next_out, &mut avail_out) };
let written = buffer.len() - avail_out;
all_output.extend_from_slice(&buffer[..written]);
if status == JxlEncoderStatus_JXL_ENC_SUCCESS {
return Ok(all_output);
} else if status == JxlEncoderStatus_JXL_ENC_NEED_MORE_OUTPUT {
continue;
} else {
return Err(Error::Encode("JXL encoding failed".into()));
}
}
}
}
impl Drop for Encoder {
fn drop(&mut self) {
unsafe {
JxlEncoderDestroy(self.ptr);
}
}
}
unsafe fn check_status(status: JxlEncoderStatus, context: &str) -> Result<()> {
if status == JxlEncoderStatus_JXL_ENC_SUCCESS {
Ok(())
} else {
Err(Error::Encode(format!("jxl {context}: status {status}")))
}
}