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(), 11001);
80            assert_eq!(JxlEncoderVersion(), 11001);
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(decoder, &pixel_format, &mut size);
147                    jxl_dec_assert!(status, "BufferSize");
148
149                    buffer.resize(size, 0f32);
150                    status = JxlDecoderSetImageOutBuffer(
151                        decoder,
152                        &pixel_format,
153                        buffer.as_mut_ptr().cast(),
154                        size,
155                    );
156                    jxl_dec_assert!(status, "SetBuffer");
157                }
158
159                FullImage => {}
160                Success => {
161                    assert_eq!(buffer.len(), (x_size * y_size * 3) as usize);
162                    return;
163                }
164                _ => panic!("Unknown decoder status: {status:#?}"),
165            }
166        }
167    }
168
169    #[test]
170    #[cfg_attr(coverage_nightly, coverage(off))]
171    fn test_bindings_decoding() {
172        unsafe {
173            let dec = JxlDecoderCreate(ptr::null()); // Default memory manager
174            assert!(!dec.is_null());
175
176            // Simple single thread runner
177            decode(dec, SAMPLE_JXL);
178
179            JxlDecoderDestroy(dec);
180        }
181    }
182
183    #[test]
184    #[cfg_attr(coverage_nightly, coverage(off))]
185    fn test_bindings_thread_pool() {
186        unsafe {
187            let runner = JxlThreadParallelRunnerCreate(
188                std::ptr::null(),
189                JxlThreadParallelRunnerDefaultNumWorkerThreads(),
190            );
191
192            let dec = JxlDecoderCreate(ptr::null()); // Default memory manager
193            assert!(!dec.is_null());
194
195            // Parallel multi-thread runner
196            let status = JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner);
197            jxl_dec_assert!(status, "Set Parallel Runner");
198
199            decode(dec, SAMPLE_JXL);
200
201            JxlDecoderDestroy(dec);
202            JxlThreadParallelRunnerDestroy(runner);
203        }
204    }
205
206    #[test]
207    #[cfg_attr(coverage_nightly, coverage(off))]
208    fn test_bindings_resizable() {
209        use JxlDecoderStatus::{
210            BasicInfo, Error, FullImage, NeedImageOutBuffer, NeedMoreInput, Success,
211        };
212
213        use crate::threads::resizable_parallel_runner::{
214            JxlResizableParallelRunner, JxlResizableParallelRunnerCreate,
215            JxlResizableParallelRunnerDestroy, JxlResizableParallelRunnerSetThreads,
216            JxlResizableParallelRunnerSuggestThreads,
217        };
218
219        unsafe {
220            let runner = JxlResizableParallelRunnerCreate(std::ptr::null());
221
222            let dec = JxlDecoderCreate(ptr::null()); // Default memory manager
223            assert!(!dec.is_null());
224
225            // Resizable parallel multi-thread runner
226            let status = JxlDecoderSetParallelRunner(dec, JxlResizableParallelRunner, runner);
227            jxl_dec_assert!(status, "Set Parallel Runner");
228
229            // Stop after getting the basic info and decoding the image
230            let mut status = JxlDecoderSubscribeEvents(
231                dec,
232                jxl_dec_events!(JxlDecoderStatus::BasicInfo, JxlDecoderStatus::FullImage),
233            );
234            jxl_dec_assert!(status, "Subscribe Events");
235
236            // Read everything in memory
237            let signature = JxlSignatureCheck(SAMPLE_JXL.as_ptr(), 2);
238            assert_eq!(signature, JxlSignature::Codestream);
239
240            let next_in = SAMPLE_JXL.as_ptr();
241            let avail_in = SAMPLE_JXL.len();
242
243            let pixel_format = JxlPixelFormat {
244                num_channels: 3,
245                data_type: JxlDataType::Uint8,
246                endianness: JxlEndianness::Native,
247                align: 0,
248            };
249
250            let mut basic_info;
251            let mut buffer: Vec<f32> = Vec::new();
252            let mut x_size = 0;
253            let mut y_size = 0;
254
255            status = JxlDecoderSetInput(dec, next_in, avail_in);
256            jxl_dec_assert!(status, "Set input");
257
258            loop {
259                status = JxlDecoderProcessInput(dec);
260
261                match status {
262                    Error => panic!("Decoder error!"),
263                    NeedMoreInput => {
264                        panic!("Error, already provided all input")
265                    }
266
267                    // Get the basic info
268                    BasicInfo => {
269                        basic_info = {
270                            let mut info = MaybeUninit::uninit();
271                            status = JxlDecoderGetBasicInfo(dec, info.as_mut_ptr());
272                            jxl_dec_assert!(status, "BasicInfo");
273                            info.assume_init()
274                        };
275                        x_size = basic_info.xsize;
276                        y_size = basic_info.ysize;
277
278                        let num_threads = JxlResizableParallelRunnerSuggestThreads(
279                            u64::from(x_size),
280                            u64::from(y_size),
281                        );
282                        JxlResizableParallelRunnerSetThreads(runner, num_threads as usize);
283
284                        assert_eq!(basic_info.xsize, 40);
285                        assert_eq!(basic_info.ysize, 50);
286                    }
287
288                    // Get the output buffer
289                    NeedImageOutBuffer => {
290                        let mut size = 0;
291                        status = JxlDecoderImageOutBufferSize(dec, &pixel_format, &mut size);
292                        jxl_dec_assert!(status, "BufferSize");
293
294                        buffer.resize(size, 0f32);
295                        status = JxlDecoderSetImageOutBuffer(
296                            dec,
297                            &pixel_format,
298                            buffer.as_mut_ptr().cast(),
299                            size,
300                        );
301                        jxl_dec_assert!(status, "SetBuffer");
302                    }
303
304                    FullImage => {}
305                    Success => {
306                        assert_eq!(buffer.len(), (x_size * y_size * 3) as usize);
307                        break;
308                    }
309                    _ => panic!("Unknown decoder status: {status:#?}"),
310                }
311            }
312
313            JxlDecoderDestroy(dec);
314            JxlResizableParallelRunnerDestroy(runner);
315        }
316    }
317
318    #[cfg_attr(coverage_nightly, coverage(off))]
319    fn encode(pixels: &[u8], x_size: u32, ysize: u32) -> Vec<u8> {
320        unsafe {
321            let enc = JxlEncoderCreate(std::ptr::null());
322
323            let runner = JxlThreadParallelRunnerCreate(
324                std::ptr::null(),
325                JxlThreadParallelRunnerDefaultNumWorkerThreads(),
326            );
327
328            let mut status = JxlEncoderSetParallelRunner(enc, JxlThreadParallelRunner, runner);
329            jxl_enc_assert!(status, "Set Parallel Runner");
330
331            let mut basic_info = {
332                let mut basic_info = MaybeUninit::uninit();
333                JxlEncoderInitBasicInfo(basic_info.as_mut_ptr());
334                basic_info.assume_init()
335            };
336            basic_info.xsize = x_size;
337            basic_info.ysize = ysize;
338
339            status = JxlEncoderSetBasicInfo(enc, &basic_info);
340            jxl_enc_assert!(status, "Set Basic Info");
341
342            let pixel_format = JxlPixelFormat {
343                num_channels: 3,
344                data_type: JxlDataType::Uint8,
345                endianness: JxlEndianness::Native,
346                align: 0,
347            };
348            let mut color_encoding = MaybeUninit::uninit();
349            JxlColorEncodingSetToSRGB(color_encoding.as_mut_ptr(), false.into());
350            status = JxlEncoderSetColorEncoding(enc, color_encoding.as_ptr());
351            jxl_enc_assert!(status, "Set Color Encoding");
352
353            status = JxlEncoderAddImageFrame(
354                JxlEncoderFrameSettingsCreate(enc, std::ptr::null()),
355                &pixel_format,
356                pixels.as_ptr().cast_mut().cast(),
357                pixels.len(),
358            );
359            jxl_enc_assert!(status, "Add Image Frame");
360
361            JxlEncoderCloseInput(enc);
362
363            let chunk_size = 1024 * 512; // 512 KB is a good initial value
364            let mut buffer = vec![0u8; chunk_size];
365            let mut next_out = buffer.as_mut_ptr();
366            let mut avail_out = chunk_size;
367
368            loop {
369                status =
370                    JxlEncoderProcessOutput(enc, std::ptr::addr_of_mut!(next_out), &mut avail_out);
371
372                if status != JxlEncoderStatus::NeedMoreOutput {
373                    break;
374                }
375
376                let offset = next_out as usize - buffer.as_ptr() as usize;
377                buffer.resize(buffer.len() * 2, 0);
378                next_out = buffer.as_mut_ptr().add(offset);
379                avail_out = buffer.len() - offset;
380            }
381            buffer.truncate(next_out as usize - buffer.as_ptr() as usize);
382            jxl_enc_assert!(status, "Encoding");
383
384            JxlEncoderDestroy(enc);
385            JxlThreadParallelRunnerDestroy(runner);
386
387            buffer
388        }
389    }
390
391    #[test]
392    #[cfg_attr(coverage_nightly, coverage(off))]
393    fn test_bindings_encoding() {
394        let img = image::load_from_memory_with_format(SAMPLE_PNG, image::ImageFormat::Png).unwrap();
395        let image_buffer = img.into_rgb8();
396
397        let output = encode(
398            image_buffer.as_raw(),
399            image_buffer.width(),
400            image_buffer.height(),
401        );
402
403        unsafe {
404            let runner = JxlThreadParallelRunnerCreate(
405                std::ptr::null(),
406                JxlThreadParallelRunnerDefaultNumWorkerThreads(),
407            );
408
409            let dec = JxlDecoderCreate(ptr::null()); // Default memory manager
410            assert!(!dec.is_null());
411
412            let status = JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner);
413            jxl_dec_assert!(status, "Set Parallel Runner");
414
415            decode(dec, &output);
416
417            JxlDecoderDestroy(dec);
418            JxlThreadParallelRunnerDestroy(runner);
419        }
420    }
421}