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}