1use alloc::{borrow::Cow, format, vec, vec::Vec};
7use core::cmp::min;
8use core::mem::size_of;
9use no_std_io::io::Write;
10
11use crate::buffer::ConvertBuffer;
12use crate::color::{FromColor, Luma, LumaA, Rgb, Rgba};
13use crate::error::{
14 EncodingError, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
15};
16use crate::{ExtendedColorType, ImageBuffer, ImageEncoder, ImageFormat, Pixel};
17use crate::{ImageError, ImageResult};
18
19use bytemuck::{try_cast_slice, try_cast_slice_mut, Pod, PodCastError};
20use num_traits::Zero;
21use ravif::{BitDepth, Encoder, Img, RGB8, RGBA8};
22use rgb::AsPixels;
23
24pub struct AvifEncoder<W> {
28 inner: W,
29 encoder: Encoder<'static>,
30}
31
32#[derive(Debug, Copy, Clone, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum ColorSpace {
36 Srgb,
38 Bt709,
40}
41
42impl ColorSpace {
43 fn to_ravif(self) -> ravif::ColorModel {
44 match self {
45 Self::Srgb => ravif::ColorModel::RGB,
46 Self::Bt709 => ravif::ColorModel::YCbCr,
47 }
48 }
49}
50
51enum RgbColor<'buf> {
52 Rgb8(Img<&'buf [RGB8]>),
53 Rgba8(Img<&'buf [RGBA8]>),
54}
55
56impl<W: Write> AvifEncoder<W> {
57 pub fn new(w: W) -> Self {
59 AvifEncoder::new_with_speed_quality(w, 4, 80) }
61
62 pub fn new_with_speed_quality(w: W, speed: u8, quality: u8) -> Self {
67 let quality = min(quality, 100);
69 let speed = min(speed, 10);
70
71 let encoder = Encoder::new()
72 .with_quality(f32::from(quality))
73 .with_alpha_quality(f32::from(quality))
74 .with_speed(speed)
75 .with_bit_depth(BitDepth::Eight);
76
77 AvifEncoder { inner: w, encoder }
78 }
79
80 pub fn with_colorspace(mut self, color_space: ColorSpace) -> Self {
82 self.encoder = self
83 .encoder
84 .with_internal_color_model(color_space.to_ravif());
85 self
86 }
87
88 pub fn with_num_threads(mut self, num_threads: Option<usize>) -> Self {
91 self.encoder = self.encoder.with_num_threads(num_threads);
92 self
93 }
94}
95
96impl<W: Write> ImageEncoder for AvifEncoder<W> {
97 #[track_caller]
103 fn write_image(
104 mut self,
105 data: &[u8],
106 width: u32,
107 height: u32,
108 color: ExtendedColorType,
109 ) -> ImageResult<()> {
110 let expected_buffer_len = color.buffer_size(width, height);
111 assert_eq!(
112 expected_buffer_len,
113 data.len() as u64,
114 "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
115 data.len(),
116 );
117
118 self.set_color(color);
119 let mut fallback = vec![]; let result = match Self::encode_as_img(&mut fallback, data, width, height, color)? {
124 RgbColor::Rgb8(buffer) => self.encoder.encode_rgb(buffer),
125 RgbColor::Rgba8(buffer) => self.encoder.encode_rgba(buffer),
126 };
127 let data = result.map_err(|err| {
128 ImageError::Encoding(EncodingError::new(ImageFormat::Avif.into(), err))
129 })?;
130 self.inner.write_all(&data.avif_file)?;
131 Ok(())
132 }
133
134 fn set_exif_metadata(&mut self, exif: Vec<u8>) -> Result<(), UnsupportedError> {
135 let encoder = core::mem::replace(&mut self.encoder, Encoder::new());
136 self.encoder = encoder.with_exif(exif);
137 Ok(())
138 }
139}
140
141impl<W: Write> AvifEncoder<W> {
142 fn set_color(&mut self, _color: ExtendedColorType) {
144 }
146
147 fn encode_as_img<'buf>(
148 fallback: &'buf mut Vec<u8>,
149 data: &'buf [u8],
150 width: u32,
151 height: u32,
152 color: ExtendedColorType,
153 ) -> ImageResult<RgbColor<'buf>> {
154 fn try_from_raw<P: Pixel + 'static>(
156 data: &[P::Subpixel],
157 width: u32,
158 height: u32,
159 ) -> ImageResult<ImageBuffer<P, &[P::Subpixel]>> {
160 ImageBuffer::from_raw(width, height, data).ok_or_else(|| {
161 ImageError::Parameter(ParameterError::from_kind(
162 ParameterErrorKind::DimensionMismatch,
163 ))
164 })
165 }
166
167 fn convert_into<'buf, P>(
169 buf: &'buf mut Vec<u8>,
170 image: ImageBuffer<P, &[P::Subpixel]>,
171 ) -> Img<&'buf [RGBA8]>
172 where
173 P: Pixel + 'static,
174 Rgba<u8>: FromColor<P>,
175 {
176 let (width, height) = image.dimensions();
177 let image: ImageBuffer<Rgba<u8>, _> = image.convert();
179 *buf = image.into_raw();
180 Img::new(buf.as_pixels(), width as usize, height as usize)
181 }
182
183 fn cast_buffer<Channel>(buf: &[u8]) -> ImageResult<Cow<'_, [Channel]>>
186 where
187 Channel: Pod + Zero,
188 {
189 match try_cast_slice(buf) {
190 Ok(slice) => Ok(Cow::Borrowed(slice)),
191 Err(PodCastError::OutputSliceWouldHaveSlop) => Err(ImageError::Parameter(
192 ParameterError::from_kind(ParameterErrorKind::DimensionMismatch),
193 )),
194 Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) => {
195 if !buf.len().is_multiple_of(size_of::<Channel>()) {
198 Err(ImageError::Parameter(ParameterError::from_kind(
199 ParameterErrorKind::DimensionMismatch,
200 )))
201 } else {
202 let len = buf.len() / size_of::<Channel>();
203 let mut data = vec![Channel::zero(); len];
204 let view = try_cast_slice_mut::<_, u8>(data.as_mut_slice()).unwrap();
205 view.copy_from_slice(buf);
206 Ok(Cow::Owned(data))
207 }
208 }
209 Err(err) => {
210 Err(ImageError::Parameter(ParameterError::from_kind(
212 ParameterErrorKind::Generic(format!("{err:?}")),
213 )))
214 }
215 }
216 }
217
218 match color {
219 ExtendedColorType::Rgb8 => {
220 let img = try_from_raw::<Rgb<u8>>(data, width, height)?;
222 if img.pixels().len() == 0 {
225 return Err(ImageError::Parameter(ParameterError::from_kind(
226 ParameterErrorKind::DimensionMismatch,
227 )));
228 }
229
230 Ok(RgbColor::Rgb8(Img::new(
231 AsPixels::as_pixels(data),
232 width as usize,
233 height as usize,
234 )))
235 }
236 ExtendedColorType::Rgba8 => {
237 let img = try_from_raw::<Rgba<u8>>(data, width, height)?;
239 if img.pixels().len() == 0 {
242 return Err(ImageError::Parameter(ParameterError::from_kind(
243 ParameterErrorKind::DimensionMismatch,
244 )));
245 }
246
247 Ok(RgbColor::Rgba8(Img::new(
248 AsPixels::as_pixels(data),
249 width as usize,
250 height as usize,
251 )))
252 }
253 ExtendedColorType::L8 => {
255 let image = try_from_raw::<Luma<u8>>(data, width, height)?;
256 Ok(RgbColor::Rgba8(convert_into(fallback, image)))
257 }
258 ExtendedColorType::La8 => {
259 let image = try_from_raw::<LumaA<u8>>(data, width, height)?;
260 Ok(RgbColor::Rgba8(convert_into(fallback, image)))
261 }
262 ExtendedColorType::L16 => {
264 let buffer = cast_buffer(data)?;
265 let image = try_from_raw::<Luma<u16>>(&buffer, width, height)?;
266 Ok(RgbColor::Rgba8(convert_into(fallback, image)))
267 }
268 ExtendedColorType::La16 => {
269 let buffer = cast_buffer(data)?;
270 let image = try_from_raw::<LumaA<u16>>(&buffer, width, height)?;
271 Ok(RgbColor::Rgba8(convert_into(fallback, image)))
272 }
273 ExtendedColorType::Rgb16 => {
274 let buffer = cast_buffer(data)?;
275 let image = try_from_raw::<Rgb<u16>>(&buffer, width, height)?;
276 Ok(RgbColor::Rgba8(convert_into(fallback, image)))
277 }
278 ExtendedColorType::Rgba16 => {
279 let buffer = cast_buffer(data)?;
280 let image = try_from_raw::<Rgba<u16>>(&buffer, width, height)?;
281 Ok(RgbColor::Rgba8(convert_into(fallback, image)))
282 }
283 _ => Err(ImageError::Unsupported(
285 UnsupportedError::from_format_and_kind(
286 ImageFormat::Avif.into(),
287 UnsupportedErrorKind::Color(color),
288 ),
289 )),
290 }
291 }
292}