Skip to content

Commit 2db5bfe

Browse files
committed
feat: Allow custom color encoding
1 parent d2895e1 commit 2db5bfe

6 files changed

Lines changed: 67 additions & 14 deletions

File tree

.harper-dictionary.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
butteraugli
2+
scRGB

.rumdl.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[global]
2+
line-length = 100

jpegxl-rs/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ encoder.quality = 3.0;
102102

103103
### [`image`](https://crates.io/crates/image) crate integration
104104

105-
By default, this integration uses the `image` integration. If you don't need it, turn off the `image` feature.
105+
By default, this integration uses the `image` integration.
106+
If you don't need it, turn off the `image` feature.
106107

107108
```rust
108109
use jpegxl_rs::image::ToDynamic;

jpegxl-rs/src/encode.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ pub struct JxlEncoder<'prl, 'mm> {
8686
pub quality: f32,
8787
/// Configure the encoder to use the JPEG XL container format
8888
///
89-
/// Using the JPEG XL container format allows to store metadata such as JPEG reconstruction;
89+
/// Using the JPEG XL container format allows one to store metadata such as JPEG reconstruction;
9090
/// but it adds a few bytes to the encoded file for container headers
9191
/// even if there is no extra metadata.
9292
pub use_container: bool,
@@ -95,7 +95,7 @@ pub struct JxlEncoder<'prl, 'mm> {
9595
/// If the input image has a color profile, it will be used for the encoded image.
9696
/// Otherwise, an internal fixed color profile is chosen (which should be smaller).
9797
///
98-
/// When lossless recompressing JPEG image, you must set this to true.
98+
/// When lossless re-compressing JPEG image, you must set this to true.
9999
///
100100
/// Default: `false`
101101
pub uses_original_profile: bool,
@@ -111,9 +111,13 @@ pub struct JxlEncoder<'prl, 'mm> {
111111

112112
/// Set color encoding
113113
///
114-
/// Default: SRGB for uint, Linear SRGB for float
114+
/// Default: sRGB for int, Linear sRGB for float
115115
pub color_encoding: Option<ColorEncoding>,
116116

117+
/// Set HDR target intensity.
118+
/// Specify the target intensity in nits for 1.0 value
119+
pub target_intensity: Option<f32>,
120+
117121
/// Set parallel runner
118122
///
119123
/// Default: `None`, indicating single thread execution
@@ -145,6 +149,7 @@ impl<'prl, 'mm> JxlEncoder<'prl, 'mm> {
145149
#[builder(default)] decoding_speed: i64,
146150
init_buffer_size: Option<usize>,
147151
color_encoding: Option<ColorEncoding>,
152+
target_intensity: Option<f32>,
148153
parallel_runner: Option<&'prl dyn ParallelRunner>,
149154
#[builder(default)] use_box: bool,
150155
) -> Result<Self, EncodeError> {
@@ -173,6 +178,7 @@ impl<'prl, 'mm> JxlEncoder<'prl, 'mm> {
173178
decoding_speed,
174179
init_buffer_size: init_buffer_size.map_or(512 * 1024, |v| if v < 32 { 32 } else { v }),
175180
color_encoding,
181+
target_intensity,
176182
parallel_runner,
177183
use_box,
178184
memory_manager,
@@ -295,18 +301,21 @@ impl JxlEncoder<'_, '_> {
295301
basic_info.num_color_channels = 1;
296302
}
297303

304+
if let Some(target_intensity) = self.target_intensity {
305+
basic_info.intensity_target = target_intensity;
306+
}
307+
298308
if let Some(pr) = self.parallel_runner {
299309
pr.callback_basic_info(&basic_info);
300310
}
301311

302312
self.check_enc_status(unsafe { JxlEncoderSetBasicInfo(self.enc, &raw const basic_info) })?;
303313

304-
if let Some(color_encoding) = self.color_encoding {
314+
if let Some(color_encoding) = &self.color_encoding {
305315
self.check_enc_status(unsafe {
306316
JxlEncoderSetColorEncoding(self.enc, &color_encoding.into())
307317
})?;
308318
}
309-
310319
Ok(())
311320
}
312321

jpegxl-rs/src/encode/options.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,23 @@ pub enum EncoderSpeed {
2929
}
3030

3131
/// Encoding color profile
32-
#[derive(Debug, Clone, Copy)]
32+
#[derive(Debug, Clone)]
3333
pub enum ColorEncoding {
34-
/// SRGB, default for uint pixel types
34+
/// sRGB, default for int pixel types
3535
Srgb,
36-
/// Linear SRGB, default for float pixel types
36+
/// Linear sRGB, default for float pixel types
3737
LinearSrgb,
38-
/// SRGB, images with only luma channel
38+
/// sRGB, images with only luma channel
3939
SrgbLuma,
40-
/// Linear SRGB with only luma channel
40+
/// Linear sRGB with only luma channel
4141
LinearSrgbLuma,
42+
/// Custom
43+
Custom(JxlColorEncoding),
4244
}
4345

44-
impl From<ColorEncoding> for JxlColorEncoding {
45-
fn from(val: ColorEncoding) -> Self {
46-
use ColorEncoding::{LinearSrgb, LinearSrgbLuma, Srgb, SrgbLuma};
46+
impl From<&ColorEncoding> for JxlColorEncoding {
47+
fn from(val: &ColorEncoding) -> Self {
48+
use ColorEncoding::{Custom, LinearSrgb, LinearSrgbLuma, Srgb, SrgbLuma};
4749

4850
let mut color_encoding = MaybeUninit::uninit();
4951

@@ -59,6 +61,9 @@ impl From<ColorEncoding> for JxlColorEncoding {
5961
LinearSrgbLuma => {
6062
api::JxlColorEncodingSetToLinearSRGB(color_encoding.as_mut_ptr(), true.into());
6163
}
64+
Custom(e) => {
65+
return e.clone();
66+
}
6267
}
6368
color_encoding.assume_init()
6469
}

jpegxl-rs/src/tests/encode.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717

1818
use half::f16;
1919
use image::DynamicImage;
20+
use jpegxl_sys::color::color_encoding::{
21+
JxlColorEncoding, JxlColorSpace, JxlPrimaries, JxlRenderingIntent, JxlTransferFunction,
22+
JxlWhitePoint,
23+
};
2024
use pretty_assertions::assert_eq;
2125
use testresult::TestResult;
2226

@@ -227,3 +231,33 @@ fn initial_buffer() -> TestResult {
227231
let _: EncoderResult<f32> = encoder.encode(sample.as_raw(), sample.width(), sample.height())?;
228232
Ok(())
229233
}
234+
235+
#[test]
236+
fn custom_color_encoding() -> TestResult {
237+
let mut encoder = encoder_builder().build()?;
238+
let sample = get_sample().to_rgb8();
239+
240+
// scRGB: linear transfer with sRGB primaries and D65 white point.
241+
let custom_color_encoding = JxlColorEncoding {
242+
color_space: JxlColorSpace::Rgb,
243+
white_point: JxlWhitePoint::D65,
244+
white_point_xy: [0.0, 0.0],
245+
primaries: JxlPrimaries::SRgb,
246+
primaries_red_xy: [0.0, 0.0],
247+
primaries_green_xy: [0.0, 0.0],
248+
primaries_blue_xy: [0.0, 0.0],
249+
transfer_function: JxlTransferFunction::Linear,
250+
gamma: 0.0,
251+
rendering_intent: JxlRenderingIntent::Relative,
252+
};
253+
encoder.color_encoding = Some(ColorEncoding::Custom(custom_color_encoding));
254+
encoder.target_intensity = Some(1000.0);
255+
256+
let result: EncoderResult<u16> =
257+
encoder.encode(sample.as_raw(), sample.width(), sample.height())?;
258+
259+
let decoder = decoder_builder().build()?;
260+
let _res = decoder.decode(&result)?;
261+
262+
Ok(())
263+
}

0 commit comments

Comments
 (0)