image/codecs/avif/
decoder.rs

1//! Decoding of AVIF images.
2use crate::error::{
3    DecodingError, ImageFormatHint, LimitError, LimitErrorKind, UnsupportedError,
4    UnsupportedErrorKind,
5};
6use crate::{ColorType, ImageDecoder, ImageError, ImageFormat, ImageResult};
7///
8/// The [AVIF] specification defines an image derivative of the AV1 bitstream, an open video codec.
9///
10/// [AVIF]: https://aomediacodec.github.io/av1-avif/
11use std::error::Error;
12use std::fmt::{Display, Formatter};
13use std::io::Read;
14use std::marker::PhantomData;
15
16use crate::codecs::avif::yuv::*;
17use dav1d::{PixelLayout, PlanarImageComponent};
18use mp4parse::{read_avif, ParseStrictness};
19
20fn error_map<E: Into<Box<dyn Error + Send + Sync>>>(err: E) -> ImageError {
21    ImageError::Decoding(DecodingError::new(ImageFormat::Avif.into(), err))
22}
23
24/// AVIF Decoder.
25///
26/// Reads one image into the chosen input.
27pub struct AvifDecoder<R> {
28    inner: PhantomData<R>,
29    picture: dav1d::Picture,
30    alpha_picture: Option<dav1d::Picture>,
31    icc_profile: Option<Vec<u8>>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35enum AvifDecoderError {
36    AlphaPlaneFormat(PixelLayout),
37    YuvLayoutOnIdentityMatrix(PixelLayout),
38}
39
40impl Display for AvifDecoderError {
41    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
42        match self {
43            AvifDecoderError::AlphaPlaneFormat(pixel_layout) => match pixel_layout {
44                PixelLayout::I400 => unreachable!("This option must be handled correctly"),
45                PixelLayout::I420 => f.write_str("Alpha layout must be 4:0:0 but it was 4:2:0"),
46                PixelLayout::I422 => f.write_str("Alpha layout must be 4:0:0 but it was 4:2:2"),
47                PixelLayout::I444 => f.write_str("Alpha layout must be 4:0:0 but it was 4:4:4"),
48            },
49            AvifDecoderError::YuvLayoutOnIdentityMatrix(pixel_layout) => match pixel_layout {
50                PixelLayout::I400 => {
51                    f.write_str("YUV layout on 'Identity' matrix must be 4:4:4 but it was 4:0:0")
52                }
53                PixelLayout::I420 => {
54                    f.write_str("YUV layout on 'Identity' matrix must be 4:4:4 but it was 4:2:0")
55                }
56                PixelLayout::I422 => {
57                    f.write_str("YUV layout on 'Identity' matrix must be 4:4:4 but it was 4:2:2")
58                }
59                PixelLayout::I444 => unreachable!("This option must be handled correctly"),
60            },
61        }
62    }
63}
64
65impl Error for AvifDecoderError {}
66
67impl<R: Read> AvifDecoder<R> {
68    /// Create a new decoder that reads its input from `r`.
69    pub fn new(mut r: R) -> ImageResult<Self> {
70        let ctx = read_avif(&mut r, ParseStrictness::Normal).map_err(error_map)?;
71        let coded = ctx.primary_item_coded_data().unwrap_or_default();
72
73        let mut primary_decoder = dav1d::Decoder::new().map_err(error_map)?;
74        primary_decoder
75            .send_data(coded.to_vec(), None, None, None)
76            .map_err(error_map)?;
77        let picture = read_until_ready(&mut primary_decoder)?;
78        let alpha_item = ctx.alpha_item_coded_data().unwrap_or_default();
79        let alpha_picture = if !alpha_item.is_empty() {
80            let mut alpha_decoder = dav1d::Decoder::new().map_err(error_map)?;
81            alpha_decoder
82                .send_data(alpha_item.to_vec(), None, None, None)
83                .map_err(error_map)?;
84            Some(read_until_ready(&mut alpha_decoder)?)
85        } else {
86            None
87        };
88        let icc_profile = ctx
89            .icc_colour_information()
90            .map(|x| x.ok().unwrap_or_default())
91            .map(|x| x.to_vec());
92
93        match picture.bit_depth() {
94            8 => (),
95            10 | 12 => (),
96            _ => {
97                return ImageResult::Err(ImageError::Decoding(DecodingError::new(
98                    ImageFormatHint::Exact(ImageFormat::Avif),
99                    format!(
100                        "Avif format does not support {} bit depth",
101                        picture.bit_depth()
102                    ),
103                )))
104            }
105        };
106        Ok(AvifDecoder {
107            inner: PhantomData,
108            picture,
109            alpha_picture,
110            icc_profile,
111        })
112    }
113}
114
115/// Reshaping incorrectly aligned or sized FFI data into Rust constraints
116fn reshape_plane(source: &[u8], stride: usize, width: usize, height: usize) -> Vec<u16> {
117    let mut target_plane = vec![0u16; width * height];
118    for (shaped_row, src_row) in target_plane
119        .chunks_exact_mut(width)
120        .zip(source.chunks_exact(stride))
121    {
122        for (dst, src) in shaped_row.iter_mut().zip(src_row.chunks_exact(2)) {
123            *dst = u16::from_ne_bytes([src[0], src[1]]);
124        }
125    }
126    target_plane
127}
128
129struct Plane16View<'a> {
130    data: std::borrow::Cow<'a, [u16]>,
131    stride: usize,
132}
133
134impl Default for Plane16View<'_> {
135    fn default() -> Self {
136        Plane16View {
137            data: std::borrow::Cow::Owned(vec![]),
138            stride: 0,
139        }
140    }
141}
142
143/// This is correct to transmute FFI data for Y plane and Alpha plane
144fn transmute_y_plane16(
145    plane: &dav1d::Plane,
146    stride: usize,
147    width: usize,
148    height: usize,
149) -> Plane16View {
150    let mut y_plane_stride = stride >> 1;
151
152    let mut bind_y = vec![];
153    let plane_ref = plane.as_ref();
154
155    let mut shape_y_plane = || {
156        y_plane_stride = width;
157        bind_y = reshape_plane(plane_ref, stride, width, height);
158    };
159
160    if stride & 1 == 0 {
161        match bytemuck::try_cast_slice(plane_ref) {
162            Ok(slice) => Plane16View {
163                data: std::borrow::Cow::Borrowed(slice),
164                stride: y_plane_stride,
165            },
166            Err(_) => {
167                shape_y_plane();
168                Plane16View {
169                    data: std::borrow::Cow::Owned(bind_y),
170                    stride: y_plane_stride,
171                }
172            }
173        }
174    } else {
175        shape_y_plane();
176        Plane16View {
177            data: std::borrow::Cow::Owned(bind_y),
178            stride: y_plane_stride,
179        }
180    }
181}
182
183/// This is correct to transmute FFI data for Y plane and Alpha plane
184fn transmute_chroma_plane16(
185    plane: &dav1d::Plane,
186    pixel_layout: PixelLayout,
187    stride: usize,
188    width: usize,
189    height: usize,
190) -> Plane16View {
191    let plane_ref = plane.as_ref();
192    let mut chroma_plane_stride = stride >> 1;
193    let mut bind_chroma = vec![];
194
195    let mut shape_chroma_plane = || {
196        chroma_plane_stride = match pixel_layout {
197            PixelLayout::I400 => unreachable!(),
198            PixelLayout::I420 | PixelLayout::I422 => (width + 1) / 2,
199            PixelLayout::I444 => width,
200        };
201        let u_plane_height = match pixel_layout {
202            PixelLayout::I400 => unreachable!(),
203            PixelLayout::I420 => (height + 1) / 2,
204            PixelLayout::I422 | PixelLayout::I444 => height,
205        };
206        bind_chroma = reshape_plane(plane_ref, stride, chroma_plane_stride, u_plane_height);
207    };
208
209    if stride & 1 == 0 {
210        match bytemuck::try_cast_slice(plane_ref) {
211            Ok(slice) => Plane16View {
212                data: std::borrow::Cow::Borrowed(slice),
213                stride: chroma_plane_stride,
214            },
215            Err(_) => {
216                shape_chroma_plane();
217                Plane16View {
218                    data: std::borrow::Cow::Owned(bind_chroma),
219                    stride: chroma_plane_stride,
220                }
221            }
222        }
223    } else {
224        shape_chroma_plane();
225        Plane16View {
226            data: std::borrow::Cow::Owned(bind_chroma),
227            stride: chroma_plane_stride,
228        }
229    }
230}
231
232/// Getting one of prebuilt matrix of fails
233fn get_matrix(
234    david_matrix: dav1d::pixel::MatrixCoefficients,
235) -> Result<YuvStandardMatrix, ImageError> {
236    match david_matrix {
237        dav1d::pixel::MatrixCoefficients::Identity => Ok(YuvStandardMatrix::Identity),
238        dav1d::pixel::MatrixCoefficients::BT709 => Ok(YuvStandardMatrix::Bt709),
239        // This is arguable, some applications prefer to go with Bt.709 as default,
240        // and some applications prefer Bt.601 as default.
241        // For ex. `Chrome` always prefer Bt.709 even for SD content
242        // However, nowadays standard should be Bt.709 for HD+ size otherwise Bt.601
243        dav1d::pixel::MatrixCoefficients::Unspecified => Ok(YuvStandardMatrix::Bt709),
244        dav1d::pixel::MatrixCoefficients::Reserved => Err(ImageError::Unsupported(
245            UnsupportedError::from_format_and_kind(
246                ImageFormat::Avif.into(),
247                UnsupportedErrorKind::GenericFeature(
248                    "Using 'Reserved' color matrix is not supported".to_string(),
249                ),
250            ),
251        )),
252        dav1d::pixel::MatrixCoefficients::BT470M => Ok(YuvStandardMatrix::Bt470_6),
253        dav1d::pixel::MatrixCoefficients::BT470BG => Ok(YuvStandardMatrix::Bt601),
254        dav1d::pixel::MatrixCoefficients::ST170M => Ok(YuvStandardMatrix::Smpte240),
255        dav1d::pixel::MatrixCoefficients::ST240M => Ok(YuvStandardMatrix::Smpte240),
256        // This is an experimental matrix in libavif yet.
257        dav1d::pixel::MatrixCoefficients::YCgCo => Err(ImageError::Unsupported(
258            UnsupportedError::from_format_and_kind(
259                ImageFormat::Avif.into(),
260                UnsupportedErrorKind::GenericFeature("YCgCo matrix is not supported".to_string()),
261            ),
262        )),
263        dav1d::pixel::MatrixCoefficients::BT2020NonConstantLuminance => {
264            Ok(YuvStandardMatrix::Bt2020)
265        }
266        dav1d::pixel::MatrixCoefficients::BT2020ConstantLuminance => {
267            // This matrix significantly differs from others because linearize values is required
268            // to compute Y instead of Y'.
269            // Actually it is almost everywhere is not implemented.
270            // Libavif + libheif missing this also so actually AVIF images
271            // with CL BT.2020 might be made only by mistake
272            Err(ImageError::Unsupported(
273                UnsupportedError::from_format_and_kind(
274                    ImageFormat::Avif.into(),
275                    UnsupportedErrorKind::GenericFeature(
276                        "BT2020ConstantLuminance matrix is not supported".to_string(),
277                    ),
278                ),
279            ))
280        }
281        dav1d::pixel::MatrixCoefficients::ST2085 => Err(ImageError::Unsupported(
282            UnsupportedError::from_format_and_kind(
283                ImageFormat::Avif.into(),
284                UnsupportedErrorKind::GenericFeature("ST2085 matrix is not supported".to_string()),
285            ),
286        )),
287        dav1d::pixel::MatrixCoefficients::ChromaticityDerivedConstantLuminance
288        | dav1d::pixel::MatrixCoefficients::ChromaticityDerivedNonConstantLuminance => Err(
289            ImageError::Unsupported(UnsupportedError::from_format_and_kind(
290                ImageFormat::Avif.into(),
291                UnsupportedErrorKind::GenericFeature(
292                    "Chromaticity Derived Luminance matrix is not supported".to_string(),
293                ),
294            )),
295        ),
296        dav1d::pixel::MatrixCoefficients::ICtCp => Err(ImageError::Unsupported(
297            UnsupportedError::from_format_and_kind(
298                ImageFormat::Avif.into(),
299                UnsupportedErrorKind::GenericFeature(
300                    "ICtCp Derived Luminance matrix is not supported".to_string(),
301                ),
302            ),
303        )),
304    }
305}
306
307impl<R: Read> ImageDecoder for AvifDecoder<R> {
308    fn dimensions(&self) -> (u32, u32) {
309        (self.picture.width(), self.picture.height())
310    }
311
312    fn color_type(&self) -> ColorType {
313        if self.picture.bit_depth() == 8 {
314            ColorType::Rgba8
315        } else {
316            ColorType::Rgba16
317        }
318    }
319
320    fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
321        Ok(self.icc_profile.clone())
322    }
323
324    fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
325        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
326
327        let bit_depth = self.picture.bit_depth();
328
329        // Normally this should never happen,
330        // if this happens then there is an incorrect implementation somewhere else
331        assert!(bit_depth == 8 || bit_depth == 10 || bit_depth == 12);
332
333        let (width, height) = self.dimensions();
334        // This is suspicious if this happens, better fail early
335        if width == 0 || height == 0 {
336            return Err(ImageError::Limits(LimitError::from_kind(
337                LimitErrorKind::DimensionError,
338            )));
339        }
340
341        let yuv_range = match self.picture.color_range() {
342            dav1d::pixel::YUVRange::Limited => YuvIntensityRange::Tv,
343            dav1d::pixel::YUVRange::Full => YuvIntensityRange::Pc,
344        };
345
346        let color_matrix = get_matrix(self.picture.matrix_coefficients())?;
347
348        // Identity matrix should be possible only on 4:4:4
349        if color_matrix == YuvStandardMatrix::Identity
350            && self.picture.pixel_layout() != PixelLayout::I444
351        {
352            return Err(ImageError::Decoding(DecodingError::new(
353                ImageFormat::Avif.into(),
354                AvifDecoderError::YuvLayoutOnIdentityMatrix(self.picture.pixel_layout()),
355            )));
356        }
357
358        if bit_depth == 8 {
359            let ref_y = self.picture.plane(PlanarImageComponent::Y);
360            let ref_u = self.picture.plane(PlanarImageComponent::U);
361            let ref_v = self.picture.plane(PlanarImageComponent::V);
362
363            let image = YuvPlanarImage {
364                y_plane: ref_y.as_ref(),
365                y_stride: self.picture.stride(PlanarImageComponent::Y) as usize,
366                u_plane: ref_u.as_ref(),
367                u_stride: self.picture.stride(PlanarImageComponent::U) as usize,
368                v_plane: ref_v.as_ref(),
369                v_stride: self.picture.stride(PlanarImageComponent::V) as usize,
370                width: width as usize,
371                height: height as usize,
372            };
373
374            let worker = match self.picture.pixel_layout() {
375                PixelLayout::I400 => yuv400_to_rgba8,
376                PixelLayout::I420 => yuv420_to_rgba8,
377                PixelLayout::I422 => yuv422_to_rgba8,
378                PixelLayout::I444 => yuv444_to_rgba8,
379            };
380
381            worker(image, buf, yuv_range, color_matrix)?;
382
383            // Squashing alpha plane into a picture
384            if let Some(picture) = self.alpha_picture {
385                if picture.pixel_layout() != PixelLayout::I400 {
386                    return Err(ImageError::Decoding(DecodingError::new(
387                        ImageFormat::Avif.into(),
388                        AvifDecoderError::AlphaPlaneFormat(picture.pixel_layout()),
389                    )));
390                }
391
392                let stride = picture.stride(PlanarImageComponent::Y) as usize;
393                let plane = picture.plane(PlanarImageComponent::Y);
394
395                for (buf, slice) in Iterator::zip(
396                    buf.chunks_exact_mut(width as usize * 4),
397                    plane.as_ref().chunks_exact(stride),
398                ) {
399                    for (rgba, a_src) in buf.chunks_exact_mut(4).zip(slice) {
400                        rgba[3] = *a_src;
401                    }
402                }
403            }
404        } else {
405            // // 8+ bit-depth case
406            if let Ok(buf) = bytemuck::try_cast_slice_mut(buf) {
407                let target_slice: &mut [u16] = buf;
408                self.process_16bit_picture(target_slice, yuv_range, color_matrix)?;
409            } else {
410                // If buffer from Decoder is unaligned
411                let mut aligned_store = vec![0u16; buf.len() / 2];
412                self.process_16bit_picture(&mut aligned_store, yuv_range, color_matrix)?;
413                for (dst, src) in buf.chunks_exact_mut(2).zip(aligned_store.iter()) {
414                    let bytes = src.to_ne_bytes();
415                    dst[0] = bytes[0];
416                    dst[1] = bytes[1];
417                }
418            }
419        }
420
421        Ok(())
422    }
423
424    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
425        (*self).read_image(buf)
426    }
427}
428
429impl<R: Read> AvifDecoder<R> {
430    fn process_16bit_picture(
431        &self,
432        target: &mut [u16],
433        yuv_range: YuvIntensityRange,
434        color_matrix: YuvStandardMatrix,
435    ) -> ImageResult<()> {
436        let y_dav1d_plane = self.picture.plane(PlanarImageComponent::Y);
437
438        let (width, height) = (self.picture.width(), self.picture.height());
439        let bit_depth = self.picture.bit_depth();
440
441        // dav1d may return not aligned and not correctly constrained data,
442        // or at least I can't find guarantees on that
443        // so if it is happened, instead casting we'll need to reshape it into a target slice
444        // required criteria: bytemuck allows this align of this data, and stride must be dividable by 2
445
446        let y_plane_view = transmute_y_plane16(
447            &y_dav1d_plane,
448            self.picture.stride(PlanarImageComponent::Y) as usize,
449            width as usize,
450            height as usize,
451        );
452
453        let u_dav1d_plane = self.picture.plane(PlanarImageComponent::U);
454        let v_dav1d_plane = self.picture.plane(PlanarImageComponent::V);
455        let mut u_plane_view = Plane16View::default();
456        let mut v_plane_view = Plane16View::default();
457
458        if self.picture.pixel_layout() != PixelLayout::I400 {
459            u_plane_view = transmute_chroma_plane16(
460                &u_dav1d_plane,
461                self.picture.pixel_layout(),
462                self.picture.stride(PlanarImageComponent::U) as usize,
463                width as usize,
464                height as usize,
465            );
466            v_plane_view = transmute_chroma_plane16(
467                &v_dav1d_plane,
468                self.picture.pixel_layout(),
469                self.picture.stride(PlanarImageComponent::V) as usize,
470                width as usize,
471                height as usize,
472            );
473        }
474
475        let image = YuvPlanarImage {
476            y_plane: y_plane_view.data.as_ref(),
477            y_stride: y_plane_view.stride,
478            u_plane: u_plane_view.data.as_ref(),
479            u_stride: u_plane_view.stride,
480            v_plane: v_plane_view.data.as_ref(),
481            v_stride: v_plane_view.stride,
482            width: width as usize,
483            height: height as usize,
484        };
485
486        let worker = match self.picture.pixel_layout() {
487            PixelLayout::I400 => {
488                if bit_depth == 10 {
489                    yuv400_to_rgba10
490                } else {
491                    yuv400_to_rgba12
492                }
493            }
494            PixelLayout::I420 => {
495                if bit_depth == 10 {
496                    yuv420_to_rgba10
497                } else {
498                    yuv420_to_rgba12
499                }
500            }
501            PixelLayout::I422 => {
502                if bit_depth == 10 {
503                    yuv422_to_rgba10
504                } else {
505                    yuv422_to_rgba12
506                }
507            }
508            PixelLayout::I444 => {
509                if bit_depth == 10 {
510                    yuv444_to_rgba10
511                } else {
512                    yuv444_to_rgba12
513                }
514            }
515        };
516        worker(image, target, yuv_range, color_matrix)?;
517
518        // Squashing alpha plane into a picture
519        if let Some(picture) = &self.alpha_picture {
520            if picture.pixel_layout() != PixelLayout::I400 {
521                return Err(ImageError::Decoding(DecodingError::new(
522                    ImageFormat::Avif.into(),
523                    AvifDecoderError::AlphaPlaneFormat(picture.pixel_layout()),
524                )));
525            }
526
527            let a_dav1d_plane = picture.plane(PlanarImageComponent::Y);
528            let a_plane_view = transmute_y_plane16(
529                &a_dav1d_plane,
530                picture.stride(PlanarImageComponent::Y) as usize,
531                width as usize,
532                height as usize,
533            );
534
535            for (buf, slice) in Iterator::zip(
536                target.chunks_exact_mut(width as usize * 4),
537                a_plane_view.data.as_ref().chunks_exact(a_plane_view.stride),
538            ) {
539                for (rgba, a_src) in buf.chunks_exact_mut(4).zip(slice) {
540                    rgba[3] = *a_src;
541                }
542            }
543        }
544
545        // Expand current bit depth to target 16
546        let target_expand_bits = 16u32 - self.picture.bit_depth() as u32;
547        for item in target.iter_mut() {
548            *item = (*item << target_expand_bits) | (*item >> (16 - target_expand_bits));
549        }
550
551        Ok(())
552    }
553}
554
555/// `get_picture` and `send_pending_data` yield `Again` as a non-fatal error requesting more data is sent to the decoder
556/// This ensures that in the case of `Again` all pending data is submitted
557/// This should be called after `send_data` (which does not yield `Again` when called the first time)
558fn read_until_ready(decoder: &mut dav1d::Decoder) -> ImageResult<dav1d::Picture> {
559    loop {
560        match decoder.get_picture() {
561            Err(dav1d::Error::Again) => match decoder.send_pending_data() {
562                Ok(_) => {}
563                Err(dav1d::Error::Again) => {}
564                Err(e) => return Err(error_map(e)),
565            },
566            r => return r.map_err(error_map),
567        }
568    }
569}