Skip to main content

jpegxl_sys/
lib.rs

1/*
2This file is part of jpegxl-sys.
3
4jpegxl-sys is free software: you can redistribute it and/or modify
5it under the terms of the GNU General Public License as published by
6the Free Software Foundation, either version 3 of the License, or
7(at your option) any later version.
8
9jpegxl-sys is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License
15along with jpegxl-sys.  If not, see <https://www.gnu.org/licenses/>.
16*/
17
18#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
19
20pub mod decode;
21
22pub mod color;
23pub mod common;
24pub mod encoder;
25pub mod metadata;
26pub mod threads;
27
28#[cfg(test)]
29mod test {
30    use crate::{
31        common::types::*,
32        decode::*,
33        encoder::encode::*,
34        threads::thread_parallel_runner::{
35            JxlThreadParallelRunner, JxlThreadParallelRunnerCreate,
36            JxlThreadParallelRunnerDefaultNumWorkerThreads, JxlThreadParallelRunnerDestroy,
37        },
38    };
39
40    use std::{mem::MaybeUninit, ptr};
41
42    use pretty_assertions::assert_eq;
43
44    const SAMPLE_PNG: &[u8] = include_bytes!("../../samples/sample.png");
45    const SAMPLE_JXL: &[u8] = include_bytes!("../../samples/sample.jxl");
46
47    macro_rules! jxl_dec_events {
48        ( $( $x: expr ),* ) => {
49            {
50                let mut tmp = 0;
51                $(
52                    tmp |= $x as i32;
53                )*
54                tmp
55            }
56        };
57    }
58
59    macro_rules! jxl_dec_assert {
60        ($val:expr, $desc:expr) => {
61            if $val != JxlDecoderStatus::Success as _ {
62                panic!("Decoder error by: {:#?}, in {}", $val, $desc)
63            }
64        };
65    }
66
67    macro_rules! jxl_enc_assert {
68        ($val:expr, $desc:expr) => {
69            if $val != JxlEncoderStatus::Success as _ {
70                panic!("Encoder error by: {:#?}, in {}", $val, $desc)
71            }
72        };
73    }
74
75    #[test]
76    #[cfg_attr(coverage_nightly, coverage(off))]
77    fn test_bindings_version() {
78        unsafe {
79            assert_eq!(JxlDecoderVersion(), 11002);
80            assert_eq!(JxlEncoderVersion(), 11002);
81        }
82    }
83
84    #[cfg_attr(coverage_nightly, coverage(off))]
85    unsafe fn decode(decoder: *mut JxlDecoder, sample: &[u8]) {
86        use JxlDecoderStatus::{
87            BasicInfo, Error, FullImage, NeedImageOutBuffer, NeedMoreInput, Success,
88        };
89
90        // Stop after getting the basic info and decoding the image
91        let mut status = JxlDecoderSubscribeEvents(
92            decoder,
93            jxl_dec_events!(JxlDecoderStatus::BasicInfo, JxlDecoderStatus::FullImage),
94        );
95        jxl_dec_assert!(status, "Subscribe Events");
96
97        // Read everything in memory
98        let signature = JxlSignatureCheck(sample.as_ptr(), 2);
99        assert_eq!(signature, JxlSignature::Codestream);
100
101        let next_in = sample.as_ptr();
102        let avail_in = sample.len();
103
104        let pixel_format = JxlPixelFormat {
105            num_channels: 3,
106            data_type: JxlDataType::Uint8,
107            endianness: JxlEndianness::Native,
108            align: 0,
109        };
110
111        let mut basic_info;
112        let mut buffer: Vec<f32> = Vec::new();
113        let mut x_size = 0;
114        let mut y_size = 0;
115
116        status = JxlDecoderSetInput(decoder, next_in, avail_in);
117        jxl_dec_assert!(status, "Set input");
118
119        loop {
120            status = JxlDecoderProcessInput(decoder);
121
122            match status {
123                Error => panic!("Decoder error!"),
124                NeedMoreInput => {
125                    panic!("Error, already provided all input")
126                }
127
128                // Get the basic info
129                BasicInfo => {
130                    basic_info = {
131                        let mut info = MaybeUninit::uninit();
132                        status = JxlDecoderGetBasicInfo(decoder, info.as_mut_ptr());
133                        jxl_dec_assert!(status, "BasicInfo");
134                        info.assume_init()
135                    };
136
137                    x_size = basic_info.xsize;
138                    y_size = basic_info.ysize;
139                    assert_eq!(basic_info.xsize, 40);
140                    assert_eq!(basic_info.ysize, 50);
141                }
142
143                // Get the output buffer
144                NeedImageOutBuffer => {
145                    let mut size = 0;
146                    status = JxlDecoderImageOutBufferSize(
147                        decoder,
148                        &raw const pixel_format,
149                        &raw mut size,
150                    );
151                    jxl_dec_assert!(status, "BufferSize");
152
153                    buffer.resize(size, 0f32);
154                    status = JxlDecoderSetImageOutBuffer(
155                        decoder,
156                        &raw const pixel_format,
157                        buffer.as_mut_ptr().cast(),
158                        size,
159                    );
160                    jxl_dec_assert!(status, "SetBuffer");
161                }
162
163                FullImage => {}
164                Success => {
165                    assert_eq!(buffer.len(), (x_size * y_size * 3) as usize);
166                    return;
167                }
168                _ => panic!("Unknown decoder status: {status:#?}"),
169            }
170        }
171    }
172
173    #[test]
174    #[cfg_attr(coverage_nightly, coverage(off))]
175    fn test_bindings_decoding() {
176        unsafe {
177            let dec = JxlDecoderCreate(ptr::null()); // Default memory manager
178            assert!(!dec.is_null());
179
180            // Simple single thread runner
181            decode(dec, SAMPLE_JXL);
182
183            JxlDecoderDestroy(dec);
184        }
185    }
186
187    #[test]
188    #[cfg_attr(coverage_nightly, coverage(off))]
189    fn test_bindings_thread_pool() {
190        unsafe {
191            let runner = JxlThreadParallelRunnerCreate(
192                std::ptr::null(),
193                JxlThreadParallelRunnerDefaultNumWorkerThreads(),
194            );
195
196            let dec = JxlDecoderCreate(ptr::null()); // Default memory manager
197            assert!(!dec.is_null());
198
199            // Parallel multi-thread runner
200            let status = JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner);
201            jxl_dec_assert!(status, "Set Parallel Runner");
202
203            decode(dec, SAMPLE_JXL);
204
205            JxlDecoderDestroy(dec);
206            JxlThreadParallelRunnerDestroy(runner);
207        }
208    }
209
210    #[test]
211    #[cfg_attr(coverage_nightly, coverage(off))]
212    fn test_bindings_resizable() {
213        use JxlDecoderStatus::{
214            BasicInfo, Error, FullImage, NeedImageOutBuffer, NeedMoreInput, Success,
215        };
216
217        use crate::threads::resizable_parallel_runner::{
218            JxlResizableParallelRunner, JxlResizableParallelRunnerCreate,
219            JxlResizableParallelRunnerDestroy, JxlResizableParallelRunnerSetThreads,
220            JxlResizableParallelRunnerSuggestThreads,
221        };
222
223        unsafe {
224            let runner = JxlResizableParallelRunnerCreate(std::ptr::null());
225
226            let dec = JxlDecoderCreate(ptr::null()); // Default memory manager
227            assert!(!dec.is_null());
228
229            // Resizable parallel multi-thread runner
230            let status = JxlDecoderSetParallelRunner(dec, JxlResizableParallelRunner, runner);
231            jxl_dec_assert!(status, "Set Parallel Runner");
232
233            // Stop after getting the basic info and decoding the image
234            let mut status = JxlDecoderSubscribeEvents(
235                dec,
236                jxl_dec_events!(JxlDecoderStatus::BasicInfo, JxlDecoderStatus::FullImage),
237            );
238            jxl_dec_assert!(status, "Subscribe Events");
239
240            // Read everything in memory
241            let signature = JxlSignatureCheck(SAMPLE_JXL.as_ptr(), 2);
242            assert_eq!(signature, JxlSignature::Codestream);
243
244            let next_in = SAMPLE_JXL.as_ptr();
245            let avail_in = SAMPLE_JXL.len();
246
247            let pixel_format = JxlPixelFormat {
248                num_channels: 3,
249                data_type: JxlDataType::Uint8,
250                endianness: JxlEndianness::Native,
251                align: 0,
252            };
253
254            let mut basic_info;
255            let mut buffer: Vec<f32> = Vec::new();
256            let mut x_size = 0;
257            let mut y_size = 0;
258
259            status = JxlDecoderSetInput(dec, next_in, avail_in);
260            jxl_dec_assert!(status, "Set input");
261
262            loop {
263                status = JxlDecoderProcessInput(dec);
264
265                match status {
266                    Error => panic!("Decoder error!"),
267                    NeedMoreInput => {
268                        panic!("Error, already provided all input")
269                    }
270
271                    // Get the basic info
272                    BasicInfo => {
273                        basic_info = {
274                            let mut info = MaybeUninit::uninit();
275                            status = JxlDecoderGetBasicInfo(dec, info.as_mut_ptr());
276                            jxl_dec_assert!(status, "BasicInfo");
277                            info.assume_init()
278                        };
279                        x_size = basic_info.xsize;
280                        y_size = basic_info.ysize;
281
282                        let num_threads = JxlResizableParallelRunnerSuggestThreads(
283                            u64::from(x_size),
284                            u64::from(y_size),
285                        );
286                        JxlResizableParallelRunnerSetThreads(runner, num_threads as usize);
287
288                        assert_eq!(basic_info.xsize, 40);
289                        assert_eq!(basic_info.ysize, 50);
290                    }
291
292                    // Get the output buffer
293                    NeedImageOutBuffer => {
294                        let mut size = 0;
295                        status = JxlDecoderImageOutBufferSize(
296                            dec,
297                            &raw const pixel_format,
298                            &raw mut size,
299                        );
300                        jxl_dec_assert!(status, "BufferSize");
301
302                        buffer.resize(size, 0f32);
303                        status = JxlDecoderSetImageOutBuffer(
304                            dec,
305                            &raw const pixel_format,
306                            buffer.as_mut_ptr().cast(),
307                            size,
308                        );
309                        jxl_dec_assert!(status, "SetBuffer");
310                    }
311
312                    FullImage => {}
313                    Success => {
314                        assert_eq!(buffer.len(), (x_size * y_size * 3) as usize);
315                        break;
316                    }
317                    _ => panic!("Unknown decoder status: {status:#?}"),
318                }
319            }
320
321            JxlDecoderDestroy(dec);
322            JxlResizableParallelRunnerDestroy(runner);
323        }
324    }
325
326    #[cfg_attr(coverage_nightly, coverage(off))]
327    fn encode(pixels: &[u8], x_size: u32, ysize: u32) -> Vec<u8> {
328        unsafe {
329            let enc = JxlEncoderCreate(std::ptr::null());
330
331            let runner = JxlThreadParallelRunnerCreate(
332                std::ptr::null(),
333                JxlThreadParallelRunnerDefaultNumWorkerThreads(),
334            );
335
336            let mut status = JxlEncoderSetParallelRunner(enc, JxlThreadParallelRunner, runner);
337            jxl_enc_assert!(status, "Set Parallel Runner");
338
339            let mut basic_info = {
340                let mut basic_info = MaybeUninit::uninit();
341                JxlEncoderInitBasicInfo(basic_info.as_mut_ptr());
342                basic_info.assume_init()
343            };
344            basic_info.xsize = x_size;
345            basic_info.ysize = ysize;
346
347            status = JxlEncoderSetBasicInfo(enc, &raw const basic_info);
348            jxl_enc_assert!(status, "Set Basic Info");
349
350            let pixel_format = JxlPixelFormat {
351                num_channels: 3,
352                data_type: JxlDataType::Uint8,
353                endianness: JxlEndianness::Native,
354                align: 0,
355            };
356            let mut color_encoding = MaybeUninit::uninit();
357            JxlColorEncodingSetToSRGB(color_encoding.as_mut_ptr(), false.into());
358            status = JxlEncoderSetColorEncoding(enc, color_encoding.as_ptr());
359            jxl_enc_assert!(status, "Set Color Encoding");
360
361            status = JxlEncoderAddImageFrame(
362                JxlEncoderFrameSettingsCreate(enc, std::ptr::null()),
363                &raw const pixel_format,
364                pixels.as_ptr().cast_mut().cast(),
365                pixels.len(),
366            );
367            jxl_enc_assert!(status, "Add Image Frame");
368
369            JxlEncoderCloseInput(enc);
370
371            let chunk_size = 1024 * 512; // 512 KB is a good initial value
372            let mut buffer = vec![0u8; chunk_size];
373            let mut next_out = buffer.as_mut_ptr();
374            let mut avail_out = chunk_size;
375
376            loop {
377                status = JxlEncoderProcessOutput(
378                    enc,
379                    std::ptr::addr_of_mut!(next_out),
380                    &raw mut avail_out,
381                );
382
383                if status != JxlEncoderStatus::NeedMoreOutput {
384                    break;
385                }
386
387                let offset = next_out as usize - buffer.as_ptr() as usize;
388                buffer.resize(buffer.len() * 2, 0);
389                next_out = buffer.as_mut_ptr().add(offset);
390                avail_out = buffer.len() - offset;
391            }
392            buffer.truncate(next_out as usize - buffer.as_ptr() as usize);
393            jxl_enc_assert!(status, "Encoding");
394
395            JxlEncoderDestroy(enc);
396            JxlThreadParallelRunnerDestroy(runner);
397
398            buffer
399        }
400    }
401
402    #[test]
403    #[cfg_attr(coverage_nightly, coverage(off))]
404    fn test_bindings_encoding() {
405        let img = image::load_from_memory_with_format(SAMPLE_PNG, image::ImageFormat::Png).unwrap();
406        let image_buffer = img.into_rgb8();
407
408        let output = encode(
409            image_buffer.as_raw(),
410            image_buffer.width(),
411            image_buffer.height(),
412        );
413
414        unsafe {
415            let runner = JxlThreadParallelRunnerCreate(
416                std::ptr::null(),
417                JxlThreadParallelRunnerDefaultNumWorkerThreads(),
418            );
419
420            let dec = JxlDecoderCreate(ptr::null()); // Default memory manager
421            assert!(!dec.is_null());
422
423            let status = JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner);
424            jxl_dec_assert!(status, "Set Parallel Runner");
425
426            decode(dec, &output);
427
428            JxlDecoderDestroy(dec);
429            JxlThreadParallelRunnerDestroy(runner);
430        }
431    }
432}