Skip to main content

ai_image/io/
encoder.rs

1use crate::error::{ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind};
2use crate::{ColorType, DynamicImage, ExtendedColorType};
3use alloc::{boxed::Box, vec::Vec};
4
5/// Nominally public but DO NOT expose this type.
6///
7/// To be somewhat sure here's a compile fail test:
8///
9/// ```compile_fail
10/// use ai_image::MethodSealedToImage;
11/// ```
12///
13/// ```compile_fail
14/// use ai_image::io::MethodSealedToImage;
15/// ```
16///
17/// The same implementation strategy for a partially public trait is used in the standard library,
18/// for the different effect of forbidding `Error::type_id` overrides thus making them reliable for
19/// their calls through the `dyn` version of the trait.
20///
21/// Read more: <https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/>
22#[derive(Clone, Copy)]
23pub struct MethodSealedToImage;
24
25/// The trait all encoders implement
26pub trait ImageEncoder {
27    /// Writes all the bytes in an image to the encoder.
28    ///
29    /// This function takes a slice of bytes of the pixel data of the image
30    /// and encodes them. Just like for [`ImageDecoder::read_image`], no particular
31    /// alignment is required and data is expected to be in native endian.
32    /// The implementation will reorder the endianness as necessary for the target encoding format.
33    ///
34    /// # Panics
35    ///
36    /// Panics if `width * height * color_type.bytes_per_pixel() != buf.len()`.
37    fn write_image(
38        self,
39        buf: &[u8],
40        width: u32,
41        height: u32,
42        color_type: ExtendedColorType,
43    ) -> ImageResult<()>;
44
45    /// Set the ICC profile to use for the image.
46    ///
47    /// This function is a no-op for formats that don't support ICC profiles.
48    /// For formats that do support ICC profiles, the profile will be embedded
49    /// in the image when it is saved.
50    ///
51    /// # Errors
52    ///
53    /// This function returns an error if the format does not support ICC profiles.
54    fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> {
55        let _ = icc_profile;
56        Err(UnsupportedError::from_format_and_kind(
57            ImageFormatHint::Unknown,
58            UnsupportedErrorKind::GenericFeature(
59                "ICC profiles are not supported for this format".into(),
60            ),
61        ))
62    }
63
64    /// Set the EXIF metadata to use for the image.
65    ///
66    /// This function is a no-op for formats that don't support EXIF metadata.
67    /// For formats that do support EXIF metadata, the metadata will be embedded
68    /// in the image when it is saved.
69    ///
70    /// # Errors
71    ///
72    /// This function returns an error if the format does not support EXIF metadata or if the
73    /// encoder doesn't implement saving EXIF metadata yet.
74    fn set_exif_metadata(&mut self, exif: Vec<u8>) -> Result<(), UnsupportedError> {
75        let _ = exif;
76        Err(UnsupportedError::from_format_and_kind(
77            ImageFormatHint::Unknown,
78            UnsupportedErrorKind::GenericFeature(
79                "EXIF metadata is not supported for this format".into(),
80            ),
81        ))
82    }
83
84    /// Convert the image to a compatible format for the encoder. This is used by the encoding
85    /// methods on `DynamicImage`.
86    ///
87    /// Note that this is method is sealed to the crate and effectively pub(crate) due to the
88    /// argument type not being nameable.
89    #[doc(hidden)]
90    fn make_compatible_img(
91        &self,
92        _: MethodSealedToImage,
93        _input: &DynamicImage,
94    ) -> Option<DynamicImage> {
95        None
96    }
97}
98
99pub(crate) trait ImageEncoderBoxed: ImageEncoder {
100    fn write_image(
101        self: Box<Self>,
102        buf: &'_ [u8],
103        width: u32,
104        height: u32,
105        color: ExtendedColorType,
106    ) -> ImageResult<()>;
107}
108impl<T: ImageEncoder> ImageEncoderBoxed for T {
109    fn write_image(
110        self: Box<Self>,
111        buf: &'_ [u8],
112        width: u32,
113        height: u32,
114        color: ExtendedColorType,
115    ) -> ImageResult<()> {
116        (*self).write_image(buf, width, height, color)
117    }
118}
119
120/// Implement `dynimage_conversion_sequence` for the common case of supporting only 8-bit colors
121/// (with and without alpha).
122#[allow(unused)]
123pub(crate) fn dynimage_conversion_8bit(img: &DynamicImage) -> Option<DynamicImage> {
124    use ColorType::*;
125
126    match img.color() {
127        Rgb8 | Rgba8 | L8 | La8 => None,
128        L16 => Some(img.to_luma8().into()),
129        La16 => Some(img.to_luma_alpha8().into()),
130        Rgb16 | Rgb32F => Some(img.to_rgb8().into()),
131        Rgba16 | Rgba32F => Some(img.to_rgba8().into()),
132    }
133}