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")]
8
pub 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")]
154
pub 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
}