Add some config for jxl encode

Remove clap_num dep
This commit is contained in:
2025-09-14 11:08:46 +08:00
parent 199442ac6d
commit 5fb7a8b601
11 changed files with 183 additions and 37 deletions

View File

@@ -1,11 +1,15 @@
//! JPEG XL image support
use super::img::*;
use super::num_range::*;
use super::threadpool::*;
use crate::types::*;
use anyhow::Result;
use jpegxl_sys::common::types::*;
use jpegxl_sys::decode::*;
use jpegxl_sys::encoder::encode::*;
use jpegxl_sys::metadata::codestream_header::*;
use jpegxl_sys::threads::parallel_runner::*;
use std::ffi::c_void;
use std::io::Read;
struct JxlDecoderHandle {
@@ -32,6 +36,57 @@ impl Drop for JxlEncoderHandle {
}
}
struct ThreadPoolRunner {
thread_pool: ThreadPool<()>,
}
impl ThreadPoolRunner {
fn new(workers: usize) -> Result<Self> {
let thread_pool = ThreadPool::new(workers, Some("jxl-thread-runner-"), true)?;
Ok(Self { thread_pool })
}
}
#[derive(Clone, Copy)]
struct JpegxlPointer(*mut c_void);
unsafe impl Send for JpegxlPointer {}
unsafe extern "C-unwind" fn thread_pool_runner(
runner_opaque: *mut c_void,
jpegxl_opaque: *mut c_void,
init: JxlParallelRunInit,
func: JxlParallelRunFunction,
start_range: u32,
end_range: u32,
) -> JxlParallelRetCode {
if runner_opaque.is_null() || jpegxl_opaque.is_null() {
return JXL_PARALLEL_RET_RUNNER_ERROR;
}
let runner = unsafe { &*(runner_opaque as *const ThreadPoolRunner) };
let initre = unsafe { init(jpegxl_opaque, runner.thread_pool.size()) };
if initre != JXL_PARALLEL_RET_SUCCESS {
return initre;
}
let jpegxl = JpegxlPointer(jpegxl_opaque);
for i in start_range..end_range {
let jpegxl = jpegxl;
let func = func;
match runner.thread_pool.execute(
move |thread_id| unsafe {
let jpegxl = jpegxl;
func(jpegxl.0, i, thread_id)
},
true,
) {
Ok(_) => {}
Err(_) => return JXL_PARALLEL_RET_RUNNER_ERROR,
}
}
runner.thread_pool.join();
JXL_PARALLEL_RET_SUCCESS
}
fn check_decoder_status(status: JxlDecoderStatus) -> Result<()> {
match status {
JxlDecoderStatus::Success => Ok(()),
@@ -145,12 +200,27 @@ pub fn decode_jxl<R: Read>(mut r: R) -> Result<ImageData> {
}
/// Encode image data to JXL format
pub fn encode_jxl(mut img: ImageData, _config: &ExtraConfig) -> Result<Vec<u8>> {
pub fn encode_jxl(mut img: ImageData, config: &ExtraConfig) -> Result<Vec<u8>> {
let encoder = unsafe { JxlEncoderCreate(std::ptr::null()) };
if encoder.is_null() {
return Err(anyhow::anyhow!("Failed to create JXL encoder"));
}
let eh = JxlEncoderHandle { handle: encoder };
let ph = if config.jxl_workers > 1 {
let ph = ThreadPoolRunner::new(config.jxl_workers)?;
Some(ph)
} else {
None
};
if let Some(ph) = &ph {
check_encoder_status(unsafe {
JxlEncoderSetParallelRunner(
eh.handle,
thread_pool_runner,
ph as *const _ as *mut c_void,
)
})?;
}
let mut basic_info = default_basic_info();
basic_info.xsize = img.width;
basic_info.ysize = img.height;
@@ -184,7 +254,14 @@ pub fn encode_jxl(mut img: ImageData, _config: &ExtraConfig) -> Result<Vec<u8>>
"Failed to create JXL encoder frame settings"
));
}
check_encoder_status(unsafe { JxlEncoderSetFrameLossless(options, JxlBool::True) })?;
check_encoder_status(unsafe {
JxlEncoderSetFrameLossless(options, JxlBool::from(config.jxl_lossless))
})?;
if !config.jxl_lossless {
let distance = check_range(config.jxl_distance, 0.0, 25.0)
.map_err(|e| anyhow::anyhow!("Invalid JXL distance: {}", e))?;
check_encoder_status(unsafe { JxlEncoderSetFrameDistance(options, distance) })?;
}
let format = JxlPixelFormat {
num_channels: img.color_type.bpp(1) as u32,
data_type: if img.depth <= 8 {

View File

@@ -22,6 +22,7 @@ pub mod jxl;
pub mod lossless_audio;
mod macros;
pub mod name_replacement;
pub mod num_range;
#[cfg(feature = "utils-pcm")]
pub mod pcm;
#[cfg(feature = "utils-str")]

27
src/utils/num_range.rs Normal file
View File

@@ -0,0 +1,27 @@
//! Functions for parsing numbers within a range.
/// Check if a value is within the specified range.
pub fn check_range<T>(val: T, min: T, max: T) -> Result<T, String>
where
T: PartialOrd,
T: std::fmt::Display,
{
if val < min {
return Err(format!("Value {} is less than minimum {}", val, min));
} else if val > max {
return Err(format!("Value {} is greater than maximum {}", val, max));
}
Ok(val)
}
/// Parse a number from a string and check if it is within the specified range.
pub fn number_range<T>(s: &str, min: T, max: T) -> Result<T, String>
where
T: std::str::FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
T: PartialOrd,
T: std::fmt::Display,
{
debug_assert!(min <= max, "min should be less than or equal to max");
let val = s.parse::<T>().map_err(|e| format!("{}", e))?;
check_range(val, min, max)
}

View File

@@ -7,7 +7,7 @@ use std::sync::{
};
use std::thread::{self, JoinHandle};
type Job<T> = Box<dyn FnOnce() -> T + Send + 'static>;
type Job<T> = Box<dyn FnOnce(usize) -> T + Send + 'static>;
/// A simple generic thread pool.
///
@@ -61,7 +61,12 @@ impl<T: Send + 'static> ThreadPool<T> {
/// the channel is full, further submissions will block or return error depending on the flag.
///
/// * `name` - Optional base name for worker threads. If None, "threadpool-worker-" is used.
pub fn new<'a>(size: usize, name: Option<&'a str>) -> Result<Self, std::io::Error> {
/// * `no_result` - If true, results are not stored (saves some overhead if not needed).
pub fn new<'a>(
size: usize,
name: Option<&'a str>,
no_result: bool,
) -> Result<Self, std::io::Error> {
if size == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
@@ -98,8 +103,8 @@ impl<T: Send + 'static> ThreadPool<T> {
match job {
Ok(job) => {
// Execute the job and store result
let res = job();
{
let res = job(id);
if !no_result {
let mut r = results_clone.lock_blocking();
r.push(res);
}
@@ -135,9 +140,11 @@ impl<T: Send + 'static> ThreadPool<T> {
/// Execute a task. If `block_if_full` is true, this call will block when the internal
/// submission channel is full (i.e. all workers busy and buffer full) until space becomes available.
/// If `block_if_full` is false, this returns Err(ExecuteError::Full) when the channel is full.
///
/// job: a closure that takes the worker id (0..size-1) and returns a T.
pub fn execute<F>(&self, job: F, block_if_full: bool) -> Result<(), ExecuteError>
where
F: FnOnce() -> T + Send + 'static,
F: FnOnce(usize) -> T + Send + 'static,
{
let sender = match &self.sender {
Some(s) => s,
@@ -191,6 +198,12 @@ impl<T: Send + 'static> ThreadPool<T> {
}
}
/// Take all results, leaving an empty results vector.
pub fn take_results(&self) -> Vec<T> {
let mut results = self.results.lock_blocking();
results.split_off(0)
}
/// Wait until all submitted tasks have completed, then return the results.
pub fn into_results(self) -> Vec<T> {
self.join();