jpegxl_rs/
image.rs

1/*
2 * This file is part of jpegxl-rs.
3 *
4 * jpegxl-rs is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * jpegxl-rs is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with jpegxl-rs.  If not, see <https://www.gnu.org/licenses/>.
16 */
17
18//! `image` crate integration
19
20use std::mem::MaybeUninit;
21
22use image::{DynamicImage, ImageBuffer};
23use jpegxl_sys::common::types::{JxlDataType, JxlPixelFormat};
24
25use crate::{
26    common::PixelType,
27    decode::{JxlDecoder, Metadata},
28    DecodeError,
29};
30
31/// Extension trait for [`JxlDecoder`]
32pub trait ToDynamic {
33    /// Decode the JPEG XL image to a [`DynamicImage`]
34    ///
35    /// # Errors
36    /// Return a [`DecodeError`] when internal decoding fails.
37    /// Return `Ok(None)` when the image is not representable as a [`DynamicImage`]
38    fn decode_to_image(&self, data: &[u8]) -> Result<Option<DynamicImage>, DecodeError>;
39
40    /// Decode the JPEG XL image to a [`DynamicImage`] with a specific pixel type
41    ///
42    /// # Errors
43    /// Return a [`DecodeError`] when internal decoding fails.
44    /// Return `Ok(None)` when the image is not representable as a [`DynamicImage`]
45    fn decode_to_image_with<T: PixelType>(
46        &self,
47        data: &[u8],
48    ) -> Result<Option<DynamicImage>, DecodeError>;
49}
50
51impl<'pr, 'mm> ToDynamic for JxlDecoder<'pr, 'mm> {
52    fn decode_to_image(&self, data: &[u8]) -> Result<Option<DynamicImage>, DecodeError> {
53        let mut buffer = vec![];
54        let mut pixel_format = MaybeUninit::uninit();
55        let metadata = self.decode_internal(
56            data,
57            None,
58            false,
59            None,
60            pixel_format.as_mut_ptr(),
61            &mut buffer,
62        )?;
63
64        let pixel_format = unsafe { pixel_format.assume_init() };
65        Ok(to_image(metadata, &pixel_format, buffer))
66    }
67
68    fn decode_to_image_with<T: PixelType>(
69        &self,
70        data: &[u8],
71    ) -> Result<Option<DynamicImage>, DecodeError> {
72        let mut buffer = vec![];
73        let mut pixel_format = MaybeUninit::uninit();
74        let metadata = self.decode_internal(
75            data,
76            Some(T::pixel_type()),
77            false,
78            None,
79            pixel_format.as_mut_ptr(),
80            &mut buffer,
81        )?;
82
83        let pixel_format = unsafe { pixel_format.assume_init() };
84        Ok(to_image(metadata, &pixel_format, buffer))
85    }
86}
87
88fn to_image(
89    Metadata { width, height, .. }: Metadata,
90    pixel_format: &JxlPixelFormat,
91    buffer: Vec<u8>,
92) -> Option<DynamicImage> {
93    match (pixel_format.data_type, pixel_format.num_channels) {
94        (JxlDataType::Float, 3) => {
95            ImageBuffer::from_raw(width, height, f32::convert(&buffer, pixel_format))
96                .map(DynamicImage::ImageRgb32F)
97        }
98        (JxlDataType::Float, 4) => {
99            ImageBuffer::from_raw(width, height, f32::convert(&buffer, pixel_format))
100                .map(DynamicImage::ImageRgba32F)
101        }
102        (JxlDataType::Uint8, 1) => {
103            ImageBuffer::from_raw(width, height, buffer).map(DynamicImage::ImageLuma8)
104        }
105        (JxlDataType::Uint8, 2) => {
106            ImageBuffer::from_raw(width, height, buffer).map(DynamicImage::ImageLumaA8)
107        }
108        (JxlDataType::Uint8, 3) => {
109            ImageBuffer::from_raw(width, height, buffer).map(DynamicImage::ImageRgb8)
110        }
111        (JxlDataType::Uint8, 4) => {
112            ImageBuffer::from_raw(width, height, buffer).map(DynamicImage::ImageRgba8)
113        }
114        (JxlDataType::Uint16, 1) => {
115            ImageBuffer::from_raw(width, height, u16::convert(&buffer, pixel_format))
116                .map(DynamicImage::ImageLuma16)
117        }
118        (JxlDataType::Uint16, 2) => {
119            ImageBuffer::from_raw(width, height, u16::convert(&buffer, pixel_format))
120                .map(DynamicImage::ImageLumaA16)
121        }
122        (JxlDataType::Uint16, 3) => {
123            ImageBuffer::from_raw(width, height, u16::convert(&buffer, pixel_format))
124                .map(DynamicImage::ImageRgb16)
125        }
126        (JxlDataType::Uint16, 4) => {
127            ImageBuffer::from_raw(width, height, u16::convert(&buffer, pixel_format))
128                .map(DynamicImage::ImageRgba16)
129        }
130        _ => None,
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use crate::{
138        decode::PixelFormat,
139        decoder_builder,
140        tests::{SAMPLE_JXL, SAMPLE_JXL_GRAY, SAMPLE_PNG},
141        ThreadsRunner,
142    };
143
144    use half::f16;
145    use pretty_assertions::assert_eq;
146    use testresult::TestResult;
147
148    #[test]
149    #[cfg_attr(coverage_nightly, coverage(off))]
150    fn invalid() -> TestResult {
151        let decoder = decoder_builder().build()?;
152        assert!(decoder.decode_to_image(&[]).is_err());
153        assert!(decoder.decode_to_image_with::<f32>(&[]).is_err());
154        Ok(())
155    }
156
157    #[test]
158    #[cfg_attr(coverage_nightly, coverage(off))]
159    fn simple() -> TestResult {
160        let parallel_runner = ThreadsRunner::default();
161        let decoder = decoder_builder()
162            .parallel_runner(&parallel_runner)
163            .build()?;
164
165        let img = decoder
166            .decode_to_image(SAMPLE_JXL)?
167            .expect("Failed to create DynamicImage");
168        let sample_png = image::load_from_memory_with_format(SAMPLE_PNG, image::ImageFormat::Png)?;
169        assert_eq!(img.to_rgba16(), sample_png.to_rgba16());
170
171        Ok(())
172    }
173
174    #[test]
175    #[cfg_attr(coverage_nightly, coverage(off))]
176    fn pixel_type() -> TestResult {
177        let parallel_runner = ThreadsRunner::default();
178        let mut decoder = decoder_builder()
179            .parallel_runner(&parallel_runner)
180            .build()?;
181        assert!(decoder.decode_to_image_with::<f16>(SAMPLE_JXL)?.is_none());
182
183        decoder.pixel_format = Some(PixelFormat {
184            num_channels: 1,
185            ..PixelFormat::default()
186        });
187        decoder
188            .decode_to_image_with::<u8>(SAMPLE_JXL_GRAY)?
189            .unwrap();
190        decoder
191            .decode_to_image_with::<u16>(SAMPLE_JXL_GRAY)?
192            .unwrap();
193        assert!(decoder
194            .decode_to_image_with::<f32>(SAMPLE_JXL_GRAY)?
195            .is_none());
196
197        decoder.pixel_format = Some(PixelFormat {
198            num_channels: 2,
199            ..PixelFormat::default()
200        });
201        decoder
202            .decode_to_image_with::<u8>(SAMPLE_JXL_GRAY)?
203            .unwrap();
204        decoder
205            .decode_to_image_with::<u16>(SAMPLE_JXL_GRAY)?
206            .unwrap();
207        assert!(decoder
208            .decode_to_image_with::<f32>(SAMPLE_JXL_GRAY)?
209            .is_none());
210
211        decoder.pixel_format = Some(PixelFormat {
212            num_channels: 3,
213            ..PixelFormat::default()
214        });
215        decoder.decode_to_image_with::<u8>(SAMPLE_JXL)?.unwrap();
216        decoder.decode_to_image_with::<u16>(SAMPLE_JXL)?.unwrap();
217        decoder.decode_to_image_with::<f32>(SAMPLE_JXL)?.unwrap();
218
219        decoder.pixel_format = Some(PixelFormat {
220            num_channels: 4,
221            ..PixelFormat::default()
222        });
223        decoder.decode_to_image_with::<u8>(SAMPLE_JXL)?.unwrap();
224        decoder.decode_to_image_with::<u16>(SAMPLE_JXL)?.unwrap();
225        decoder.decode_to_image_with::<f32>(SAMPLE_JXL)?.unwrap();
226
227        Ok(())
228    }
229}