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                DecodeImageError::InsufficientMemory => write!(
32                    f,
33                    "Error decoding image: Not enough memory available to perform encoding \
34                     operation"
35                ),
36                DecodeImageError::DimensionError => {
37                    write!(f, "Error decoding image: Wrong dimensions")
38                }
39                DecodeImageError::UnsupportedImageFormat => {
40                    write!(f, "Error decoding image: Invalid data format")
41                }
42                DecodeImageError::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    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::*;
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 => image::ColorType::Rgb8,
214            RawImageFormat::RGBA8 => image::ColorType::Rgba8,
215            RawImageFormat::BGR8 => image::ColorType::Rgb8, // TODO: ???
216            RawImageFormat::BGRA8 => image::ColorType::Rgba8, // TODO: ???
217            RawImageFormat::R16 => image::ColorType::L16,
218            RawImageFormat::RG16 => image::ColorType::La16,
219            RawImageFormat::RGB16 => image::ColorType::Rgb16,
220            RawImageFormat::RGBA16 => image::ColorType::Rgba16,
221            RawImageFormat::RGBF32 => image::ColorType::Rgb32F,
222            RawImageFormat::RGBAF32 => image::ColorType::Rgba32F,
223        }
224    }
225
226    fn translate_image_error_encode(i: ImageError) -> EncodeImageError {
227        match i {
228            ImageError::Limits(l) => match l.kind() {
229                LimitErrorKind::InsufficientMemory => EncodeImageError::InsufficientMemory,
230                LimitErrorKind::DimensionError => EncodeImageError::DimensionError,
231                _ => EncodeImageError::Unknown,
232            },
233            _ => EncodeImageError::Unknown,
234        }
235    }
236
237    impl_result!(
238        U8Vec,
239        EncodeImageError,
240        ResultU8VecEncodeImageError,
241        copy = false,
242        [Debug, Clone]
243    );
244
245    macro_rules! encode_func {
246        ($func:ident, $encoder:ident, $feature:expr) => {
247            #[cfg(feature = $feature)]
248            pub fn $func(image: &RawImage) -> ResultU8VecEncodeImageError {
249                let mut result = Vec::<u8>::new();
250
251                {
252                    let mut cursor = Cursor::new(&mut result);
253                    let mut encoder = $encoder::new(&mut cursor);
254                    let pixels = match image.pixels.get_u8_vec_ref() {
255                        Some(s) => s,
256                        None => {
257                            return ResultU8VecEncodeImageError::Err(EncodeImageError::InvalidData);
258                        }
259                    };
260
261                    if let Err(e) = encoder.encode(
262                        pixels.as_ref(),
263                        image.width as u32,
264                        image.height as u32,
265                        translate_rawimage_colortype(image.data_format).into(),
266                    ) {
267                        return ResultU8VecEncodeImageError::Err(translate_image_error_encode(e));
268                    }
269                }
270
271                ResultU8VecEncodeImageError::Ok(result.into())
272            }
273
274            #[cfg(not(feature = $feature))]
275            pub fn $func(image: &RawImage) -> ResultU8VecEncodeImageError {
276                ResultU8VecEncodeImageError::Err(EncodeImageError::EncoderNotAvailable)
277            }
278        };
279    }
280
281    encode_func!(encode_bmp, BmpEncoder, "bmp");
282    encode_func!(encode_tga, TgaEncoder, "tga");
283    encode_func!(encode_tiff, TiffEncoder, "tiff");
284    encode_func!(encode_gif, GifEncoder, "gif");
285    encode_func!(encode_pnm, PnmEncoder, "pnm");
286
287    #[cfg(feature = "png")]
288    pub fn encode_png(image: &RawImage) -> ResultU8VecEncodeImageError {
289        use image::ImageEncoder;
290
291        let mut result = Vec::<u8>::new();
292
293        {
294            let mut cursor = Cursor::new(&mut result);
295            let mut encoder = PngEncoder::new_with_quality(
296                &mut cursor,
297                image::codecs::png::CompressionType::Best,
298                image::codecs::png::FilterType::Adaptive,
299            );
300            let pixels = match image.pixels.get_u8_vec_ref() {
301                Some(s) => s,
302                None => {
303                    return ResultU8VecEncodeImageError::Err(EncodeImageError::InvalidData);
304                }
305            };
306
307            if let Err(e) = encoder.write_image(
308                pixels.as_ref(),
309                image.width as u32,
310                image.height as u32,
311                translate_rawimage_colortype(image.data_format).into(),
312            ) {
313                return ResultU8VecEncodeImageError::Err(translate_image_error_encode(e));
314            }
315        }
316
317        ResultU8VecEncodeImageError::Ok(result.into())
318    }
319
320    #[cfg(not(feature = "png"))]
321    pub fn encode_png(image: &RawImage) -> ResultU8VecEncodeImageError {
322        ResultU8VecEncodeImageError::Err(EncodeImageError::EncoderNotAvailable)
323    }
324
325    #[cfg(feature = "jpeg")]
326    pub fn encode_jpeg(image: &RawImage, quality: u8) -> ResultU8VecEncodeImageError {
327        let mut result = Vec::<u8>::new();
328
329        {
330            let mut cursor = Cursor::new(&mut result);
331            let mut encoder = JpegEncoder::new_with_quality(&mut cursor, quality);
332            let pixels = match image.pixels.get_u8_vec_ref() {
333                Some(s) => s,
334                None => {
335                    return ResultU8VecEncodeImageError::Err(EncodeImageError::InvalidData);
336                }
337            };
338
339            if let Err(e) = encoder.encode(
340                pixels.as_ref(),
341                image.width as u32,
342                image.height as u32,
343                translate_rawimage_colortype(image.data_format).into(),
344            ) {
345                return ResultU8VecEncodeImageError::Err(translate_image_error_encode(e));
346            }
347        }
348
349        ResultU8VecEncodeImageError::Ok(result.into())
350    }
351
352    #[cfg(not(feature = "jpeg"))]
353    pub fn encode_jpeg(image: &RawImage, quality: u8) -> ResultU8VecEncodeImageError {
354        ResultU8VecEncodeImageError::Err(EncodeImageError::EncoderNotAvailable)
355    }
356}