Skip to main content

azul_layout/
image.rs

1//! Image decoding and encoding utilities, wrapping the `image` crate
2//! with Azul's FFI-compatible types (`RawImage`, `RawImageFormat`).
3//!
4//! - [`decode`]: Decodes image bytes in any supported format into a [`RawImage`].
5//! - [`encode`]: Encodes a [`RawImage`] into various output formats (PNG, JPEG, BMP, etc.).
6
7#[cfg(feature = "std")]
8pub mod decode {
9    use core::fmt;
10
11    use azul_core::resources::{RawImage, RawImageFormat};
12    use azul_css::{impl_result, impl_result_inner, U8Vec};
13    use image::{
14        error::{ImageError, LimitError, LimitErrorKind},
15        DynamicImage,
16    };
17
18    /// Errors that can occur when decoding an image from raw bytes.
19    #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
20    #[repr(C)]
21    pub enum DecodeImageError {
22        InsufficientMemory,
23        DimensionError,
24        UnsupportedImageFormat,
25        Unknown,
26    }
27
28    impl fmt::Display for DecodeImageError {
29        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30            match self {
31                Self::InsufficientMemory => write!(
32                    f,
33                    "Error decoding image: Not enough memory available to perform encoding \
34                     operation"
35                ),
36                Self::DimensionError => {
37                    write!(f, "Error decoding image: Wrong dimensions")
38                }
39                Self::UnsupportedImageFormat => {
40                    write!(f, "Error decoding image: Invalid data format")
41                }
42                Self::Unknown => write!(f, "Error decoding image: Unknown error"),
43            }
44        }
45    }
46
47    fn translate_image_error_decode(i: ImageError) -> DecodeImageError {
48        match i {
49            ImageError::Limits(l) => match l.kind() {
50                LimitErrorKind::InsufficientMemory => DecodeImageError::InsufficientMemory,
51                LimitErrorKind::DimensionError => DecodeImageError::DimensionError,
52                _ => DecodeImageError::Unknown,
53            },
54            _ => DecodeImageError::Unknown,
55        }
56    }
57
58    impl_result!(
59        RawImage,
60        DecodeImageError,
61        ResultRawImageDecodeImageError,
62        copy = false,
63        [Debug, Clone]
64    );
65
66    /// Decodes image bytes in any supported format into a [`RawImage`].
67    ///
68    /// The image format is guessed from the byte contents. Returns the decoded
69    /// pixel data along with dimensions and format information.
70    #[must_use] pub fn decode_raw_image_from_any_bytes(image_bytes: &[u8]) -> ResultRawImageDecodeImageError {
71        use azul_core::resources::RawImageData;
72
73        let image_format = match image::guess_format(image_bytes) {
74            Ok(o) => o,
75            Err(e) => {
76                return ResultRawImageDecodeImageError::Err(translate_image_error_decode(e));
77            }
78        };
79
80        let decoded = match image::load_from_memory_with_format(image_bytes, image_format) {
81            Ok(o) => o,
82            Err(e) => {
83                return ResultRawImageDecodeImageError::Err(translate_image_error_decode(e));
84            }
85        };
86
87        let ((width, height), data_format, pixels) = match decoded {
88            DynamicImage::ImageLuma8(i) => (
89                i.dimensions(),
90                RawImageFormat::R8,
91                RawImageData::U8(i.into_vec().into()),
92            ),
93            DynamicImage::ImageLumaA8(i) => (
94                i.dimensions(),
95                RawImageFormat::RG8,
96                RawImageData::U8(i.into_vec().into()),
97            ),
98            DynamicImage::ImageRgb8(i) => (
99                i.dimensions(),
100                RawImageFormat::RGB8,
101                RawImageData::U8(i.into_vec().into()),
102            ),
103            DynamicImage::ImageRgba8(i) => (
104                i.dimensions(),
105                RawImageFormat::RGBA8,
106                RawImageData::U8(i.into_vec().into()),
107            ),
108            DynamicImage::ImageLuma16(i) => (
109                i.dimensions(),
110                RawImageFormat::R16,
111                RawImageData::U16(i.into_vec().into()),
112            ),
113            DynamicImage::ImageLumaA16(i) => (
114                i.dimensions(),
115                RawImageFormat::RG16,
116                RawImageData::U16(i.into_vec().into()),
117            ),
118            DynamicImage::ImageRgb16(i) => (
119                i.dimensions(),
120                RawImageFormat::RGB16,
121                RawImageData::U16(i.into_vec().into()),
122            ),
123            DynamicImage::ImageRgba16(i) => (
124                i.dimensions(),
125                RawImageFormat::RGBA16,
126                RawImageData::U16(i.into_vec().into()),
127            ),
128            DynamicImage::ImageRgb32F(i) => (
129                i.dimensions(),
130                RawImageFormat::RGBF32,
131                RawImageData::F32(i.into_vec().into()),
132            ),
133            DynamicImage::ImageRgba32F(i) => (
134                i.dimensions(),
135                RawImageFormat::RGBAF32,
136                RawImageData::F32(i.into_vec().into()),
137            ),
138            _ => {
139                return ResultRawImageDecodeImageError::Err(DecodeImageError::Unknown);
140            }
141        };
142
143        ResultRawImageDecodeImageError::Ok(RawImage {
144            tag: Vec::new().into(),
145            pixels,
146            width: width as usize,
147            height: height as usize,
148            premultiplied_alpha: false,
149            data_format,
150        })
151    }
152}
153#[cfg(feature = "std")]
154pub mod encode {
155    use alloc::vec::Vec;
156    use core::fmt;
157    use std::io::Cursor;
158
159    use azul_core::resources::{RawImage, RawImageFormat};
160    use azul_css::{impl_result, impl_result_inner, U8Vec};
161    #[cfg(feature = "bmp")]
162    use image::codecs::bmp::BmpEncoder;
163    #[cfg(feature = "gif")]
164    use image::codecs::gif::GifEncoder;
165#[cfg(feature = "jpeg")]
166    use image::codecs::jpeg::JpegEncoder;
167    #[cfg(feature = "png")]
168    use image::codecs::png::PngEncoder;
169    #[cfg(feature = "pnm")]
170    use image::codecs::pnm::PnmEncoder;
171    #[cfg(feature = "tga")]
172    use image::codecs::tga::TgaEncoder;
173    #[cfg(feature = "tiff")]
174    use image::codecs::tiff::TiffEncoder;
175    use image::error::{ImageError, LimitError, LimitErrorKind};
176
177    /// Errors that can occur when encoding a [`RawImage`] into a specific format.
178    #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
179    #[repr(C)]
180    pub enum EncodeImageError {
181        /// Crate was not compiled with the given encoder flags
182        EncoderNotAvailable,
183        InsufficientMemory,
184        DimensionError,
185        InvalidData,
186        Unknown,
187    }
188
189    impl fmt::Display for EncodeImageError {
190        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191            use self::EncodeImageError::{EncoderNotAvailable, InsufficientMemory, DimensionError, InvalidData, Unknown};
192            match self {
193                EncoderNotAvailable => write!(
194                    f,
195                    "Missing encoder (library was not compiled with given codec)"
196                ),
197                InsufficientMemory => write!(
198                    f,
199                    "Error encoding image: Not enough memory available to perform encoding \
200                     operation"
201                ),
202                DimensionError => write!(f, "Error encoding image: Wrong dimensions"),
203                InvalidData => write!(f, "Error encoding image: Invalid data format"),
204                Unknown => write!(f, "Error encoding image: Unknown error"),
205            }
206        }
207    }
208
209    const fn translate_rawimage_colortype(i: RawImageFormat) -> image::ColorType {
210        match i {
211            RawImageFormat::R8 => image::ColorType::L8,
212            RawImageFormat::RG8 => image::ColorType::La8,
213            RawImageFormat::RGB8 | RawImageFormat::BGR8 => image::ColorType::Rgb8,
214            RawImageFormat::RGBA8 | RawImageFormat::BGRA8 => image::ColorType::Rgba8,
215            RawImageFormat::R16 => image::ColorType::L16,
216            RawImageFormat::RG16 => image::ColorType::La16,
217            RawImageFormat::RGB16 => image::ColorType::Rgb16,
218            RawImageFormat::RGBA16 => image::ColorType::Rgba16,
219            RawImageFormat::RGBF32 => image::ColorType::Rgb32F,
220            RawImageFormat::RGBAF32 => image::ColorType::Rgba32F,
221        }
222    }
223
224    fn bgr_to_rgb_swap(pixels: &[u8], format: RawImageFormat) -> Option<Vec<u8>> {
225        match format {
226            RawImageFormat::BGR8 => {
227                let mut out = pixels.to_vec();
228                for chunk in out.chunks_exact_mut(3) {
229                    chunk.swap(0, 2);
230                }
231                Some(out)
232            }
233            RawImageFormat::BGRA8 => {
234                let mut out = pixels.to_vec();
235                for chunk in out.chunks_exact_mut(4) {
236                    chunk.swap(0, 2);
237                }
238                Some(out)
239            }
240            _ => None,
241        }
242    }
243
244    fn translate_image_error_encode(i: ImageError) -> EncodeImageError {
245        match i {
246            ImageError::Limits(l) => match l.kind() {
247                LimitErrorKind::InsufficientMemory => EncodeImageError::InsufficientMemory,
248                LimitErrorKind::DimensionError => EncodeImageError::DimensionError,
249                _ => EncodeImageError::Unknown,
250            },
251            _ => EncodeImageError::Unknown,
252        }
253    }
254
255    impl_result!(
256        U8Vec,
257        EncodeImageError,
258        ResultU8VecEncodeImageError,
259        copy = false,
260        [Debug, Clone]
261    );
262
263    macro_rules! encode_func {
264        ($func:ident, $encoder:ident, $feature:expr) => {
265            #[cfg(feature = $feature)]
266            pub fn $func(image: &RawImage) -> ResultU8VecEncodeImageError {
267                let width = match u32::try_from(image.width) {
268                    Ok(w) => w,
269                    Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
270                };
271                let height = match u32::try_from(image.height) {
272                    Ok(h) => h,
273                    Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
274                };
275                let mut result = Vec::<u8>::new();
276
277                {
278                    let mut cursor = Cursor::new(&mut result);
279                    let mut encoder = $encoder::new(&mut cursor);
280                    let pixels = match image.pixels.get_u8_vec_ref() {
281                        Some(s) => s,
282                        None => {
283                            return ResultU8VecEncodeImageError::Err(EncodeImageError::InvalidData);
284                        }
285                    };
286
287                    let swapped = bgr_to_rgb_swap(pixels.as_ref(), image.data_format);
288                    let pixel_bytes = swapped.as_deref().unwrap_or(pixels.as_ref());
289
290                    if let Err(e) = encoder.encode(
291                        pixel_bytes,
292                        width,
293                        height,
294                        translate_rawimage_colortype(image.data_format).into(),
295                    ) {
296                        return ResultU8VecEncodeImageError::Err(translate_image_error_encode(e));
297                    }
298                }
299
300                ResultU8VecEncodeImageError::Ok(result.into())
301            }
302
303            #[cfg(not(feature = $feature))]
304            #[must_use] pub const fn $func(image: &RawImage) -> ResultU8VecEncodeImageError {
305                ResultU8VecEncodeImageError::Err(EncodeImageError::EncoderNotAvailable)
306            }
307        };
308    }
309
310    encode_func!(encode_bmp, BmpEncoder, "bmp");
311    encode_func!(encode_tga, TgaEncoder, "tga");
312    encode_func!(encode_tiff, TiffEncoder, "tiff");
313    encode_func!(encode_gif, GifEncoder, "gif");
314    encode_func!(encode_pnm, PnmEncoder, "pnm");
315
316    #[cfg(feature = "png")]
317    pub fn encode_png(image: &RawImage) -> ResultU8VecEncodeImageError {
318        use image::ImageEncoder;
319
320        let width = match u32::try_from(image.width) {
321            Ok(w) => w,
322            Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
323        };
324        let height = match u32::try_from(image.height) {
325            Ok(h) => h,
326            Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
327        };
328        let mut result = Vec::<u8>::new();
329
330        {
331            let mut cursor = Cursor::new(&mut result);
332            let mut encoder = PngEncoder::new_with_quality(
333                &mut cursor,
334                image::codecs::png::CompressionType::Best,
335                image::codecs::png::FilterType::Adaptive,
336            );
337            let pixels = match image.pixels.get_u8_vec_ref() {
338                Some(s) => s,
339                None => {
340                    return ResultU8VecEncodeImageError::Err(EncodeImageError::InvalidData);
341                }
342            };
343
344            let swapped = bgr_to_rgb_swap(pixels.as_ref(), image.data_format);
345            let pixel_bytes = swapped.as_deref().unwrap_or(pixels.as_ref());
346
347            if let Err(e) = encoder.write_image(
348                pixel_bytes,
349                width,
350                height,
351                translate_rawimage_colortype(image.data_format).into(),
352            ) {
353                return ResultU8VecEncodeImageError::Err(translate_image_error_encode(e));
354            }
355        }
356
357        ResultU8VecEncodeImageError::Ok(result.into())
358    }
359
360    #[cfg(not(feature = "png"))]
361    #[must_use] pub const fn encode_png(image: &RawImage) -> ResultU8VecEncodeImageError {
362        ResultU8VecEncodeImageError::Err(EncodeImageError::EncoderNotAvailable)
363    }
364
365    #[cfg(feature = "jpeg")]
366    pub fn encode_jpeg(image: &RawImage, quality: u8) -> ResultU8VecEncodeImageError {
367        let width = match u32::try_from(image.width) {
368            Ok(w) => w,
369            Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
370        };
371        let height = match u32::try_from(image.height) {
372            Ok(h) => h,
373            Err(_) => return ResultU8VecEncodeImageError::Err(EncodeImageError::DimensionError),
374        };
375        let mut result = Vec::<u8>::new();
376
377        {
378            let mut cursor = Cursor::new(&mut result);
379            let mut encoder = JpegEncoder::new_with_quality(&mut cursor, quality);
380            let pixels = match image.pixels.get_u8_vec_ref() {
381                Some(s) => s,
382                None => {
383                    return ResultU8VecEncodeImageError::Err(EncodeImageError::InvalidData);
384                }
385            };
386
387            let swapped = bgr_to_rgb_swap(pixels.as_ref(), image.data_format);
388            let pixel_bytes = swapped.as_deref().unwrap_or(pixels.as_ref());
389
390            if let Err(e) = encoder.encode(
391                pixel_bytes,
392                width,
393                height,
394                translate_rawimage_colortype(image.data_format).into(),
395            ) {
396                return ResultU8VecEncodeImageError::Err(translate_image_error_encode(e));
397            }
398        }
399
400        ResultU8VecEncodeImageError::Ok(result.into())
401    }
402
403    #[cfg(not(feature = "jpeg"))]
404    #[must_use] pub const fn encode_jpeg(image: &RawImage, quality: u8) -> ResultU8VecEncodeImageError {
405        ResultU8VecEncodeImageError::Err(EncodeImageError::EncoderNotAvailable)
406    }
407}