Repository: image-rs/image (language: rust)

Issue:
Fix avif odd width/height handling
I license past and future contributions under the dual MIT/Apache-2.0 license,
allowing licensees to choose either at their option.

[Related issue](https://github.com/image-rs/image/issues/1647)

This PR fixes the InvalidValue error in dcv_color_primitives when trying to decode odd width/height AVIF files and changes PixelLayout::I422 to todo!() since it's not supported by dcv_color_primitives [(source)](https://docs.rs/dcv-color-primitives/latest/dcv_color_primitives/fn.convert_image.html).

It's a bit hacky but dav1d's output buffer's width/stride seems to always be a multiple of 128 (not too sure about this though) so no need to account for that just increase the width provided to the convert_image  by 1 if it's odd.

And for the height add a padding row of zeros to the source luma buffer.

And at the end copy only the actual width and height parts of the dst buffer while discarding the padding.
avif decoder error 
this avif [1.avif.zip](https://github.com/image-rs/image/files/11485598/1.avif.zip)

image::guess_format is UnsupportedError

with file header[3] == 24

[src/lib.rs:46] &format = Avif
[src/lib.rs:53] &bin[..12] = [
    0,
    0,
    0,
    24,
    102,
    116,
    121,
    112,
    97,
    118,
    105,
    102,
]
[src/lib.rs:54] b"\0\0\0\x1cftypavif" = [
    0,
    0,
    0,
    28,
    102,
    116,
    121,
    112,
    97,
    118,
    105,
    102,
]

avif decoder error 
<img width="1898" alt="image" src="https://github.com/image-rs/image/assets/130831741/250e531f-d529-4e01-9a5d-1838e6788265">

[src/lib.rs:55] image::guess_format(bin) = Err(
    Unsupported(
        UnsupportedError {
            format: Unknown,
            kind: Format(
                Unknown,
            ),
        },
    ),
)
  ✘ [fail]: img → jpg Rejected promise returned by test
  ─

  img → jpg

  Rejected promise returned by test. Reason:

  Error {
    code: 'GenericFailure',
    message: 'The image format could not be determined',
  }




Discussion / hints:
Given that the `dcv-color-primitives` doesn't really seem to match our requirements well (based on the number of todo!()'s and copies involved) I wonder if it would be better just to implement the logic ourselves.

Based on that crates documentation, the relevant formula is:
```rust
r = 1.164 * (y - 16) + 1.596 * (cr - 128)
g = 1.164 * (y - 16) - 0.813 * (cr - 128) - 0.392 * (cb - 128)
b = 1.164 * (y - 16) + 2.017 * (cb - 128)
```
> Given that the `dcv-color-primitives` doesn't really seem to match our requirements well (based on the number of todo!()'s and copies involved) I wonder if it would be better just to implement the logic ourselves.

It's an option, I was also thinking about adding the yuv422 -> rgba and yuv400 -> rgba algorithms to `dcv-color-primitives`. I took a look at the crate and they seem to have a lot of the constants and other needed stuff already.




Any updates on this issue? Lots of avif images out there with odds dimensions.
> Given that the `dcv-color-primitives` doesn't really seem to match our requirements well (based on the number of todo!()'s and copies involved) I wonder if it would be better just to implement the logic ourselves.
> 
> Based on that crates documentation, the relevant formula is:
> 
> ```rust
> r = 1.164 * (y - 16) + 1.596 * (cr - 128)
> g = 1.164 * (y - 16) - 0.813 * (cr - 128) - 0.392 * (cb - 128)
> b = 1.164 * (y - 16) + 2.017 * (cb - 128)
> ```

This is correct only for Bt.601 matrix with limited range which is not really common nowadays, since it was replaced by Bt.709 matrix in most cases.
Here you can take conversion for [yuv400](https://github.com/awxkee/yuvutils-rs/blob/master/yuvs/src/yuv400.rs), [yuv420](https://github.com/awxkee/yuvutils-rs/blob/master/yuvs/src/yuv420.rs), [yuv422](https://github.com/awxkee/yuvutils-rs/blob/master/yuvs/src/yuv422.rs) and [yuv444](https://github.com/awxkee/yuvutils-rs/blob/master/yuvs/src/yuv444.rs). Those methods should be in your requirements and supports any image size combinations, and can be easily adopted for 8+ bit-depth or your specific requirements. Here is [defined standard](https://www.itu.int/rec/T-REC-H.273) if you want to check it. And its simplified inverse matrix

![inverse_matrix](https://github.com/user-attachments/assets/aaeb8cf7-8bfd-408a-99e6-b7b7309bee0f)




Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 4a6108a26c..000e85b90f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -42,7 +42,6 @@ num-traits = { version = "0.2.0" }
 # Optional dependencies
 color_quant = { version = "1.1", optional = true }
 dav1d = { version = "0.10.3", optional = true }
-dcv-color-primitives = { version = "0.6.1", optional = true }
 exr = { version = "1.5.0", optional = true }
 gif = { version = "0.13", optional = true }
 image-webp = { version = "0.2.0", optional = true }
@@ -88,7 +87,7 @@ webp = ["dep:image-webp"]
 rayon = ["dep:rayon", "ravif?/threading"] # Enables multi-threading
 nasm = ["ravif?/asm"] # Enables use of nasm by rav1e (requires nasm to be installed)
 color_quant = ["dep:color_quant"] # Enables color quantization
-avif-native = ["dep:mp4parse", "dep:dcv-color-primitives", "dep:dav1d"] # Enable native dependency libdav1d
+avif-native = ["dep:mp4parse", "dep:dav1d"] # Enable native dependency libdav1d
 benchmarks = [] # Build some inline benchmarks. Useful only during development (requires nightly Rust)
 
 [[bench]]
diff --git a/src/codecs/avif/decoder.rs b/src/codecs/avif/decoder.rs
index 15b2244b09..04e43f666a 100644
--- a/src/codecs/avif/decoder.rs
+++ b/src/codecs/avif/decoder.rs
@@ -1,17 +1,20 @@
 //! Decoding of AVIF images.
+use crate::error::{
+    DecodingError, ImageFormatHint, LimitError, LimitErrorKind, UnsupportedError,
+    UnsupportedErrorKind,
+};
+use crate::{ColorType, ImageDecoder, ImageError, ImageFormat, ImageResult};
 ///
 /// The [AVIF] specification defines an image derivative of the AV1 bitstream, an open video codec.
 ///
 /// [AVIF]: https://aomediacodec.github.io/av1-avif/
 use std::error::Error;
+use std::fmt::{Display, Formatter};
 use std::io::Read;
 use std::marker::PhantomData;
 
-use crate::error::{DecodingError, ImageFormatHint, UnsupportedError, UnsupportedErrorKind};
-use crate::{ColorType, ImageDecoder, ImageError, ImageFormat, ImageResult};
-
+use crate::codecs::avif::yuv::*;
 use dav1d::{PixelLayout, PlanarImageComponent};
-use dcv_color_primitives as dcp;
 use mp4parse::{read_avif, ParseStrictness};
 
 fn error_map<E: Into<Box<dyn Error + Send + Sync>>>(err: E) -> ImageError {
@@ -28,6 +31,39 @@ pub struct AvifDecoder<R> {
     icc_profile: Option<Vec<u8>>,
 }
 
+#[derive(Debug, Clone, PartialEq, Eq)]
+enum AvifDecoderError {
+    AlphaPlaneFormat(PixelLayout),
+    YuvLayoutOnIdentityMatrix(PixelLayout),
+}
+
+impl Display for AvifDecoderError {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            AvifDecoderError::AlphaPlaneFormat(pixel_layout) => match pixel_layout {
+                PixelLayout::I400 => unreachable!("This option must be handled correctly"),
+                PixelLayout::I420 => f.write_str("Alpha layout must be 4:0:0 but it was 4:2:0"),
+                PixelLayout::I422 => f.write_str("Alpha layout must be 4:0:0 but it was 4:2:2"),
+                PixelLayout::I444 => f.write_str("Alpha layout must be 4:0:0 but it was 4:4:4"),
+            },
+            AvifDecoderError::YuvLayoutOnIdentityMatrix(pixel_layout) => match pixel_layout {
+                PixelLayout::I400 => {
+                    f.write_str("YUV layout on 'Identity' matrix must be 4:4:4 but it was 4:0:0")
+                }
+                PixelLayout::I420 => {
+                    f.write_str("YUV layout on 'Identity' matrix must be 4:4:4 but it was 4:2:0")
+                }
+                PixelLayout::I422 => {
+                    f.write_str("YUV layout on 'Identity' matrix must be 4:4:4 but it was 4:2:2")
+                }
+                PixelLayout::I444 => unreachable!("This option must be handled correctly"),
+            },
+        }
+    }
+}
+
+impl Error for AvifDecoderError {}
+
 impl<R: Read> AvifDecoder<R> {
     /// Create a new decoder that reads its input from `r`.
     pub fn new(mut r: R) -> ImageResult<Self> {
@@ -56,17 +92,7 @@ impl<R: Read> AvifDecoder<R> {
 
         match picture.bit_depth() {
             8 => (),
-            10 | 12 => {
-                return ImageResult::Err(ImageError::Unsupported(
-                    UnsupportedError::from_format_and_kind(
-                        ImageFormatHint::Exact(ImageFormat::Avif),
-                        UnsupportedErrorKind::GenericFeature(format!(
-                            "Only 8 bit depth is supported but was {}",
-                            picture.bit_depth()
-                        )),
-                    ),
-                ))
-            }
+            10 | 12 => (),
             _ => {
                 return ImageResult::Err(ImageError::Decoding(DecodingError::new(
                     ImageFormatHint::Exact(ImageFormat::Avif),
@@ -86,13 +112,209 @@ impl<R: Read> AvifDecoder<R> {
     }
 }
 
+/// Reshaping incorrectly aligned or sized FFI data into Rust constraints
+fn reshape_plane(source: &[u8], stride: usize, width: usize, height: usize) -> Vec<u16> {
+    let mut target_plane = vec![0u16; width * height];
+    for (shaped_row, src_row) in target_plane
+        .chunks_exact_mut(width)
+        .zip(source.chunks_exact(stride))
+    {
+        for (dst, src) in shaped_row.iter_mut().zip(src_row.chunks_exact(2)) {
+            *dst = u16::from_ne_bytes([src[0], src[1]]);
+        }
+    }
+    target_plane
+}
+
+struct Plane16View<'a> {
+    data: std::borrow::Cow<'a, [u16]>,
+    stride: usize,
+}
+
+impl Default for Plane16View<'_> {
+    fn default() -> Self {
+        Plane16View {
+            data: std::borrow::Cow::Owned(vec![]),
+            stride: 0,
+        }
+    }
+}
+
+/// This is correct to transmute FFI data for Y plane and Alpha plane
+fn transmute_y_plane16(
+    plane: &dav1d::Plane,
+    stride: usize,
+    width: usize,
+    height: usize,
+) -> Plane16View {
+    let mut y_plane_stride = stride >> 1;
+
+    let mut bind_y = vec![];
+    let plane_ref = plane.as_ref();
+
+    let mut shape_y_plane = || {
+        y_plane_stride = width;
+        bind_y = reshape_plane(plane_ref, stride, width, height);
+    };
+
+    if stride & 1 == 0 {
+        match bytemuck::try_cast_slice(plane_ref) {
+            Ok(slice) => Plane16View {
+                data: std::borrow::Cow::Borrowed(slice),
+                stride: y_plane_stride,
+            },
+            Err(_) => {
+                shape_y_plane();
+                Plane16View {
+                    data: std::borrow::Cow::Owned(bind_y),
+                    stride: y_plane_stride,
+                }
+            }
+        }
+    } else {
+        shape_y_plane();
+        Plane16View {
+            data: std::borrow::Cow::Owned(bind_y),
+            stride: y_plane_stride,
+        }
+    }
+}
+
+/// This is correct to transmute FFI data for Y plane and Alpha plane
+fn transmute_chroma_plane16(
+    plane: &dav1d::Plane,
+    pixel_layout: PixelLayout,
+    stride: usize,
+    width: usize,
+    height: usize,
+) -> Plane16View {
+    let plane_ref = plane.as_ref();
+    let mut chroma_plane_stride = stride >> 1;
+    let mut bind_chroma = vec![];
+
+    let mut shape_chroma_plane = || {
+        chroma_plane_stride = match pixel_layout {
+            PixelLayout::I400 => unreachable!(),
+            PixelLayout::I420 | PixelLayout::I422 => (width + 1) / 2,
+            PixelLayout::I444 => width,
+        };
+        let u_plane_height = match pixel_layout {
+            PixelLayout::I400 => unreachable!(),
+            PixelLayout::I420 => (height + 1) / 2,
+            PixelLayout::I422 | PixelLayout::I444 => height,
+        };
+        bind_chroma = reshape_plane(plane_ref, stride, chroma_plane_stride, u_plane_height);
+    };
+
+    if stride & 1 == 0 {
+        match bytemuck::try_cast_slice(plane_ref) {
+            Ok(slice) => Plane16View {
+                data: std::borrow::Cow::Borrowed(slice),
+                stride: chroma_plane_stride,
+            },
+            Err(_) => {
+                shape_chroma_plane();
+                Plane16View {
+                    data: std::borrow::Cow::Owned(bind_chroma),
+                    stride: chroma_plane_stride,
+                }
+            }
+        }
+    } else {
+        shape_chroma_plane();
+        Plane16View {
+            data: std::borrow::Cow::Owned(bind_chroma),
+            stride: chroma_plane_stride,
+        }
+    }
+}
+
+/// Getting one of prebuilt matrix of fails
+fn get_matrix(
+    david_matrix: dav1d::pixel::MatrixCoefficients,
+) -> Result<YuvStandardMatrix, ImageError> {
+    match david_matrix {
+        dav1d::pixel::MatrixCoefficients::Identity => Ok(YuvStandardMatrix::Identity),
+        dav1d::pixel::MatrixCoefficients::BT709 => Ok(YuvStandardMatrix::Bt709),
+        // This is arguable, some applications prefer to go with Bt.709 as default,
+        // and some applications prefer Bt.601 as default.
+        // For ex. `Chrome` always prefer Bt.709 even for SD content
+        // However, nowadays standard should be Bt.709 for HD+ size otherwise Bt.601
+        dav1d::pixel::MatrixCoefficients::Unspecified => Ok(YuvStandardMatrix::Bt709),
+        dav1d::pixel::MatrixCoefficients::Reserved => Err(ImageError::Unsupported(
+            UnsupportedError::from_format_and_kind(
+                ImageFormat::Avif.into(),
+                UnsupportedErrorKind::GenericFeature(
+                    "Using 'Reserved' color matrix is not supported".to_string(),
+                ),
+            ),
+        )),
+        dav1d::pixel::MatrixCoefficients::BT470M => Ok(YuvStandardMatrix::Bt470_6),
+        dav1d::pixel::MatrixCoefficients::BT470BG => Ok(YuvStandardMatrix::Bt601),
+        dav1d::pixel::MatrixCoefficients::ST170M => Ok(YuvStandardMatrix::Smpte240),
+        dav1d::pixel::MatrixCoefficients::ST240M => Ok(YuvStandardMatrix::Smpte240),
+        // This is an experimental matrix in libavif yet.
+        dav1d::pixel::MatrixCoefficients::YCgCo => Err(ImageError::Unsupported(
+            UnsupportedError::from_format_and_kind(
+                ImageFormat::Avif.into(),
+                UnsupportedErrorKind::GenericFeature("YCgCo matrix is not supported".to_string()),
+            ),
+        )),
+        dav1d::pixel::MatrixCoefficients::BT2020NonConstantLuminance => {
+            Ok(YuvStandardMatrix::Bt2020)
+        }
+        dav1d::pixel::MatrixCoefficients::BT2020ConstantLuminance => {
+            // This matrix significantly differs from others because linearize values is required
+            // to compute Y instead of Y'.
+            // Actually it is almost everywhere is not implemented.
+            // Libavif + libheif missing this also so actually AVIF images
+            // with CL BT.2020 might be made only by mistake
+            Err(ImageError::Unsupported(
+                UnsupportedError::from_format_and_kind(
+                    ImageFormat::Avif.into(),
+                    UnsupportedErrorKind::GenericFeature(
+                        "BT2020ConstantLuminance matrix is not supported".to_string(),
+                    ),
+                ),
+            ))
+        }
+        dav1d::pixel::MatrixCoefficients::ST2085 => Err(ImageError::Unsupported(
+            UnsupportedError::from_format_and_kind(
+                ImageFormat::Avif.into(),
+                UnsupportedErrorKind::GenericFeature("ST2085 matrix is not supported".to_string()),
+            ),
+        )),
+        dav1d::pixel::MatrixCoefficients::ChromaticityDerivedConstantLuminance
+        | dav1d::pixel::MatrixCoefficients::ChromaticityDerivedNonConstantLuminance => Err(
+            ImageError::Unsupported(UnsupportedError::from_format_and_kind(
+                ImageFormat::Avif.into(),
+                UnsupportedErrorKind::GenericFeature(
+                    "Chromaticity Derived Luminance matrix is not supported".to_string(),
+                ),
+            )),
+        ),
+        dav1d::pixel::MatrixCoefficients::ICtCp => Err(ImageError::Unsupported(
+            UnsupportedError::from_format_and_kind(
+                ImageFormat::Avif.into(),
+                UnsupportedErrorKind::GenericFeature(
+                    "ICtCp Derived Luminance matrix is not supported".to_string(),
+                ),
+            ),
+        )),
+    }
+}
+
 impl<R: Read> ImageDecoder for AvifDecoder<R> {
     fn dimensions(&self) -> (u32, u32) {
         (self.picture.width(), self.picture.height())
     }
 
     fn color_type(&self) -> ColorType {
-        ColorType::Rgba8
+        if self.picture.bit_depth() == 8 {
+            ColorType::Rgba8
+        } else {
+            ColorType::Rgba16
+        }
     }
 
     fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
@@ -102,93 +324,231 @@ impl<R: Read> ImageDecoder for AvifDecoder<R> {
     fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
         assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
 
-        if self.picture.pixel_layout() != PixelLayout::I400 {
-            let pixel_format = match self.picture.pixel_layout() {
-                PixelLayout::I400 => todo!(),
-                PixelLayout::I420 => dcp::PixelFormat::I420,
-                PixelLayout::I422 => dcp::PixelFormat::I422,
-                PixelLayout::I444 => dcp::PixelFormat::I444,
+        let bit_depth = self.picture.bit_depth();
+
+        // Normally this should never happen,
+        // if this happens then there is an incorrect implementation somewhere else
+        assert!(bit_depth == 8 || bit_depth == 10 || bit_depth == 12);
+
+        let (width, height) = self.dimensions();
+        // This is suspicious if this happens, better fail early
+        if width == 0 || height == 0 {
+            return Err(ImageError::Limits(LimitError::from_kind(
+                LimitErrorKind::DimensionError,
+            )));
+        }
+
+        let yuv_range = match self.picture.color_range() {
+            dav1d::pixel::YUVRange::Limited => YuvIntensityRange::Tv,
+            dav1d::pixel::YUVRange::Full => YuvIntensityRange::Pc,
+        };
+
+        let color_matrix = get_matrix(self.picture.matrix_coefficients())?;
+
+        // Identity matrix should be possible only on 4:4:4
+        if color_matrix == YuvStandardMatrix::Identity
+            && self.picture.pixel_layout() != PixelLayout::I444
+        {
+            return Err(ImageError::Decoding(DecodingError::new(
+                ImageFormat::Avif.into(),
+                AvifDecoderError::YuvLayoutOnIdentityMatrix(self.picture.pixel_layout()),
+            )));
+        }
+
+        if bit_depth == 8 {
+            let ref_y = self.picture.plane(PlanarImageComponent::Y);
+            let ref_u = self.picture.plane(PlanarImageComponent::U);
+            let ref_v = self.picture.plane(PlanarImageComponent::V);
+
+            let image = YuvPlanarImage {
+                y_plane: ref_y.as_ref(),
+                y_stride: self.picture.stride(PlanarImageComponent::Y) as usize,
+                u_plane: ref_u.as_ref(),
+                u_stride: self.picture.stride(PlanarImageComponent::U) as usize,
+                v_plane: ref_v.as_ref(),
+                v_stride: self.picture.stride(PlanarImageComponent::V) as usize,
+                width: width as usize,
+                height: height as usize,
+            };
+
+            let worker = match self.picture.pixel_layout() {
+                PixelLayout::I400 => yuv400_to_rgba8,
+                PixelLayout::I420 => yuv420_to_rgba8,
+                PixelLayout::I422 => yuv422_to_rgba8,
+                PixelLayout::I444 => yuv444_to_rgba8,
             };
-            let src_color_space = match (self.picture.color_primaries(), self.picture.color_range())
-            {
-                (dav1d::pixel::ColorPrimaries::BT709, dav1d::pixel::YUVRange::Full) => {
-                    dcp::ColorSpace::Bt709FR
+
+            worker(image, buf, yuv_range, color_matrix)?;
+
+            // Squashing alpha plane into a picture
+            if let Some(picture) = self.alpha_picture {
+                if picture.pixel_layout() != PixelLayout::I400 {
+                    return Err(ImageError::Decoding(DecodingError::new(
+                        ImageFormat::Avif.into(),
+                        AvifDecoderError::AlphaPlaneFormat(picture.pixel_layout()),
+                    )));
                 }
-                (dav1d::pixel::ColorPrimaries::BT709, dav1d::pixel::YUVRange::Limited) => {
-                    dcp::ColorSpace::Bt709
+
+                let stride = picture.stride(PlanarImageComponent::Y) as usize;
+                let plane = picture.plane(PlanarImageComponent::Y);
+
+                for (buf, slice) in Iterator::zip(
+                    buf.chunks_exact_mut(width as usize * 4),
+                    plane.as_ref().chunks_exact(stride),
+                ) {
+                    for (rgba, a_src) in buf.chunks_exact_mut(4).zip(slice) {
+                        rgba[3] = *a_src;
+                    }
                 }
-                (_, dav1d::pixel::YUVRange::Full) => dcp::ColorSpace::Bt601FR,
-                (_, dav1d::pixel::YUVRange::Limited) => dcp::ColorSpace::Bt601,
-            };
-            let src_format = dcp::ImageFormat {
-                pixel_format,
-                color_space: src_color_space,
-                num_planes: 3,
-            };
-            let dst_format = dcp::ImageFormat {
-                pixel_format: dcp::PixelFormat::Rgba,
-                color_space: dcp::ColorSpace::Rgb,
-                num_planes: 1,
-            };
-            let (width, height) = self.dimensions();
-            let planes = &[
-                self.picture.plane(PlanarImageComponent::Y),
-                self.picture.plane(PlanarImageComponent::U),
-                self.picture.plane(PlanarImageComponent::V),
-            ];
-            let src_buffers = planes.iter().map(AsRef::as_ref).collect::<Vec<_>>();
-            let strides = &[
-                self.picture.stride(PlanarImageComponent::Y) as usize,
+            }
+        } else {
+            // // 8+ bit-depth case
+            if let Ok(buf) = bytemuck::try_cast_slice_mut(buf) {
+                let target_slice: &mut [u16] = buf;
+                self.process_16bit_picture(target_slice, yuv_range, color_matrix)?;
+            } else {
+                // If buffer from Decoder is unaligned
+                let mut aligned_store = vec![0u16; buf.len() / 2];
+                self.process_16bit_picture(&mut aligned_store, yuv_range, color_matrix)?;
+                for (dst, src) in buf.chunks_exact_mut(2).zip(aligned_store.iter()) {
+                    let bytes = src.to_ne_bytes();
+                    dst[0] = bytes[0];
+                    dst[1] = bytes[1];
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
+        (*self).read_image(buf)
+    }
+}
+
+impl<R: Read> AvifDecoder<R> {
+    fn process_16bit_picture(
+        &self,
+        target: &mut [u16],
+        yuv_range: YuvIntensityRange,
+        color_matrix: YuvStandardMatrix,
+    ) -> ImageResult<()> {
+        let y_dav1d_plane = self.picture.plane(PlanarImageComponent::Y);
+
+        let (width, height) = (self.picture.width(), self.picture.height());
+        let bit_depth = self.picture.bit_depth();
+
+        // dav1d may return not aligned and not correctly constrained data,
+        // or at least I can't find guarantees on that
+        // so if it is happened, instead casting we'll need to reshape it into a target slice
+        // required criteria: bytemuck allows this align of this data, and stride must be dividable by 2
+
+        let y_plane_view = transmute_y_plane16(
+            &y_dav1d_plane,
+            self.picture.stride(PlanarImageComponent::Y) as usize,
+            width as usize,
+            height as usize,
+        );
+
+        let u_dav1d_plane = self.picture.plane(PlanarImageComponent::U);
+        let v_dav1d_plane = self.picture.plane(PlanarImageComponent::V);
+        let mut u_plane_view = Plane16View::default();
+        let mut v_plane_view = Plane16View::default();
+
+        if self.picture.pixel_layout() != PixelLayout::I400 {
+            u_plane_view = transmute_chroma_plane16(
+                &u_dav1d_plane,
+                self.picture.pixel_layout(),
                 self.picture.stride(PlanarImageComponent::U) as usize,
+                width as usize,
+                height as usize,
+            );
+            v_plane_view = transmute_chroma_plane16(
+                &v_dav1d_plane,
+                self.picture.pixel_layout(),
                 self.picture.stride(PlanarImageComponent::V) as usize,
-            ];
-            let dst_buffers = &mut [&mut buf[..]];
-            dcp::convert_image(
-                width,
-                height,
-                &src_format,
-                Some(strides),
-                &src_buffers,
-                &dst_format,
-                None,
-                dst_buffers,
-            )
-            .map_err(error_map)?;
-        } else {
-            let plane = self.picture.plane(PlanarImageComponent::Y);
-            buf.copy_from_slice(plane.as_ref());
+                width as usize,
+                height as usize,
+            );
         }
 
-        if let Some(picture) = self.alpha_picture {
+        let image = YuvPlanarImage {
+            y_plane: y_plane_view.data.as_ref(),
+            y_stride: y_plane_view.stride,
+            u_plane: u_plane_view.data.as_ref(),
+            u_stride: u_plane_view.stride,
+            v_plane: v_plane_view.data.as_ref(),
+            v_stride: v_plane_view.stride,
+            width: width as usize,
+            height: height as usize,
+        };
+
+        let worker = match self.picture.pixel_layout() {
+            PixelLayout::I400 => {
+                if bit_depth == 10 {
+                    yuv400_to_rgba10
+                } else {
+                    yuv400_to_rgba12
+                }
+            }
+            PixelLayout::I420 => {
+                if bit_depth == 10 {
+                    yuv420_to_rgba10
+                } else {
+                    yuv420_to_rgba12
+                }
+            }
+            PixelLayout::I422 => {
+                if bit_depth == 10 {
+                    yuv422_to_rgba10
+                } else {
+                    yuv422_to_rgba12
+                }
+            }
+            PixelLayout::I444 => {
+                if bit_depth == 10 {
+                    yuv444_to_rgba10
+                } else {
+                    yuv444_to_rgba12
+                }
+            }
+        };
+        worker(image, target, yuv_range, color_matrix)?;
+
+        // Squashing alpha plane into a picture
+        if let Some(picture) = &self.alpha_picture {
             if picture.pixel_layout() != PixelLayout::I400 {
-                return Err(ImageError::Unsupported(
-                    UnsupportedError::from_format_and_kind(
-                        ImageFormat::Avif.into(),
-                        UnsupportedErrorKind::GenericFeature(format!(
-                            "Alpha must be PixelLayout::I400 but was: {:?}",
-                            picture.pixel_layout() // PixelLayout does not implement display
-                        )),
-                    ),
-                ));
+                return Err(ImageError::Decoding(DecodingError::new(
+                    ImageFormat::Avif.into(),
+                    AvifDecoderError::AlphaPlaneFormat(picture.pixel_layout()),
+                )));
             }
-            let stride = picture.stride(PlanarImageComponent::Y) as usize;
-            let plane = picture.plane(PlanarImageComponent::Y);
-            let width = picture.width();
+
+            let a_dav1d_plane = picture.plane(PlanarImageComponent::Y);
+            let a_plane_view = transmute_y_plane16(
+                &a_dav1d_plane,
+                picture.stride(PlanarImageComponent::Y) as usize,
+                width as usize,
+                height as usize,
+            );
+
             for (buf, slice) in Iterator::zip(
-                buf.chunks_exact_mut(width as usize * 4),
-                plane.as_ref().chunks_exact(stride),
+                target.chunks_exact_mut(width as usize * 4),
+                a_plane_view.data.as_ref().chunks_exact(a_plane_view.stride),
             ) {
-                for i in 0..width as usize {
-                    buf[3 + i * 4] = slice[i];
+                for (rgba, a_src) in buf.chunks_exact_mut(4).zip(slice) {
+                    rgba[3] = *a_src;
                 }
             }
         }
 
-        Ok(())
-    }
+        // Expand current bit depth to target 16
+        let target_expand_bits = 16u32 - self.picture.bit_depth() as u32;
+        for item in target.iter_mut() {
+            *item = (*item << target_expand_bits) | (*item >> (16 - target_expand_bits));
+        }
 
-    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
-        (*self).read_image(buf)
+        Ok(())
     }
 }
 
diff --git a/src/codecs/avif/mod.rs b/src/codecs/avif/mod.rs
index 89edfc2c97..7c18cc5f30 100644
--- a/src/codecs/avif/mod.rs
+++ b/src/codecs/avif/mod.rs
@@ -12,3 +12,5 @@ pub use self::encoder::{AvifEncoder, ColorSpace};
 mod decoder;
 #[cfg(feature = "avif")]
 mod encoder;
+#[cfg(feature = "avif-native")]
+mod yuv;
diff --git a/src/codecs/avif/yuv.rs b/src/codecs/avif/yuv.rs
new file mode 100644
index 0000000000..77155b3286
--- /dev/null
+++ b/src/codecs/avif/yuv.rs
@@ -0,0 +1,1322 @@
+use crate::error::DecodingError;
+use crate::{ImageError, ImageFormat};
+use num_traits::AsPrimitive;
+use std::fmt::{Display, Formatter};
+
+#[derive(Debug, Copy, Clone)]
+/// Representation of inversion matrix
+struct CbCrInverseTransform<T> {
+    pub y_coef: T,
+    pub cr_coef: T,
+    pub cb_coef: T,
+    pub g_coeff_1: T,
+    pub g_coeff_2: T,
+}
+
+impl CbCrInverseTransform<f32> {
+    fn to_integers(self, precision: u32) -> CbCrInverseTransform<i32> {
+        let precision_scale: i32 = 1i32 << (precision as i32);
+        let cr_coef = (self.cr_coef * precision_scale as f32) as i32;
+        let cb_coef = (self.cb_coef * precision_scale as f32) as i32;
+        let y_coef = (self.y_coef * precision_scale as f32) as i32;
+        let g_coef_1 = (self.g_coeff_1 * precision_scale as f32) as i32;
+        let g_coef_2 = (self.g_coeff_2 * precision_scale as f32) as i32;
+        CbCrInverseTransform::<i32> {
+            y_coef,
+            cr_coef,
+            cb_coef,
+            g_coeff_1: g_coef_1,
+            g_coeff_2: g_coef_2,
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
+struct ErrorSize {
+    expected: usize,
+    received: usize,
+}
+
+#[derive(Copy, Clone, Debug)]
+enum PlaneDefinition {
+    Y,
+    U,
+    V,
+}
+
+impl Display for PlaneDefinition {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        match self {
+            PlaneDefinition::Y => f.write_str("Luma"),
+            PlaneDefinition::U => f.write_str("U chroma"),
+            PlaneDefinition::V => f.write_str("V chroma"),
+        }
+    }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum YuvConversionError {
+    YuvPlaneSizeMismatch(PlaneDefinition, ErrorSize),
+    RgbDestinationSizeMismatch(ErrorSize),
+}
+
+impl Display for YuvConversionError {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            YuvConversionError::YuvPlaneSizeMismatch(plane, error_size) => {
+                f.write_fmt(format_args!(
+                    "For plane {} expected size is {} but was received {}",
+                    plane, error_size.received, error_size.expected,
+                ))
+            }
+            YuvConversionError::RgbDestinationSizeMismatch(error_size) => {
+                f.write_fmt(format_args!(
+                    "For RGB destination expected size is {} but was received {}",
+                    error_size.received, error_size.expected,
+                ))
+            }
+        }
+    }
+}
+
+impl std::error::Error for YuvConversionError {}
+
+#[inline]
+fn check_yuv_plane_preconditions<V>(
+    plane: &[V],
+    plane_definition: PlaneDefinition,
+    stride: usize,
+    height: usize,
+) -> Result<(), ImageError> {
+    if plane.len() != stride * height {
+        return Err(ImageError::Decoding(DecodingError::new(
+            ImageFormat::Avif.into(),
+            YuvConversionError::YuvPlaneSizeMismatch(
+                plane_definition,
+                ErrorSize {
+                    expected: stride * height,
+                    received: plane.len(),
+                },
+            ),
+        )));
+    }
+    Ok(())
+}
+
+#[inline]
+fn check_rgb_preconditions<V>(
+    rgb_data: &[V],
+    stride: usize,
+    height: usize,
+) -> Result<(), ImageError> {
+    if rgb_data.len() != stride * height {
+        return Err(ImageError::Decoding(DecodingError::new(
+            ImageFormat::Avif.into(),
+            YuvConversionError::RgbDestinationSizeMismatch(ErrorSize {
+                expected: stride * height,
+                received: rgb_data.len(),
+            }),
+        )));
+    }
+    Ok(())
+}
+
+/// Transformation YUV to RGB with coefficients as specified in [ITU-R](https://www.itu.int/rec/T-REC-H.273/en)
+fn get_inverse_transform(
+    range_bgra: u32,
+    range_y: u32,
+    range_uv: u32,
+    kr: f32,
+    kb: f32,
+    precision: u32,
+) -> CbCrInverseTransform<i32> {
+    let range_uv = range_bgra as f32 / range_uv as f32;
+    let y_coef = range_bgra as f32 / range_y as f32;
+    let cr_coeff = (2f32 * (1f32 - kr)) * range_uv;
+    let cb_coeff = (2f32 * (1f32 - kb)) * range_uv;
+    let kg = 1.0f32 - kr - kb;
+    assert_ne!(kg, 0., "1.0f - kr - kg must not be 0");
+    let g_coeff_1 = (2f32 * ((1f32 - kr) * kr / kg)) * range_uv;
+    let g_coeff_2 = (2f32 * ((1f32 - kb) * kb / kg)) * range_uv;
+    let exact_transform = CbCrInverseTransform {
+        y_coef,
+        cr_coef: cr_coeff,
+        cb_coef: cb_coeff,
+        g_coeff_1,
+        g_coeff_2,
+    };
+    exact_transform.to_integers(precision)
+}
+
+#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
+/// Declares YUV range TV (limited) or PC (full),
+/// more info [ITU-R](https://www.itu.int/rec/T-REC-H.273/en)
+pub(crate) enum YuvIntensityRange {
+    /// Limited range Y ∈ [16 << (depth - 8), 16 << (depth - 8) + 224 << (depth - 8)],
+    /// UV ∈ [-1 << (depth - 1), -1 << (depth - 1) + 1 << (depth - 1)]
+    Tv,
+    /// Full range Y ∈ [0, 2^bit_depth - 1],
+    /// UV ∈ [-1 << (depth - 1), -1 << (depth - 1) + 2^bit_depth - 1]
+    Pc,
+}
+
+#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
+struct YuvChromaRange {
+    pub bias_y: u32,
+    pub bias_uv: u32,
+    pub range_y: u32,
+    pub range_uv: u32,
+    pub range: YuvIntensityRange,
+}
+
+impl YuvIntensityRange {
+    const fn get_yuv_range(self, depth: u32) -> YuvChromaRange {
+        match self {
+            YuvIntensityRange::Tv => YuvChromaRange {
+                bias_y: 16 << (depth - 8),
+                bias_uv: 1 << (depth - 1),
+                range_y: 219 << (depth - 8),
+                range_uv: 224 << (depth - 8),
+                range: self,
+            },
+            YuvIntensityRange::Pc => YuvChromaRange {
+                bias_y: 0,
+                bias_uv: 1 << (depth - 1),
+                range_uv: (1 << depth) - 1,
+                range_y: (1 << depth) - 1,
+                range: self,
+            },
+        }
+    }
+}
+
+#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
+/// Declares standard prebuilt YUV conversion matrices,
+/// check [ITU-R](https://www.itu.int/rec/T-REC-H.273/en) information for more info
+pub(crate) enum YuvStandardMatrix {
+    Bt601,
+    Bt709,
+    Bt2020,
+    Smpte240,
+    Bt470_6,
+    Identity,
+}
+
+#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
+struct YuvBias {
+    kr: f32,
+    kb: f32,
+}
+
+impl YuvStandardMatrix {
+    const fn get_kr_kb(self) -> YuvBias {
+        match self {
+            YuvStandardMatrix::Bt601 => YuvBias {
+                kr: 0.299f32,
+                kb: 0.114f32,
+            },
+            YuvStandardMatrix::Bt709 => YuvBias {
+                kr: 0.2126f32,
+                kb: 0.0722f32,
+            },
+            YuvStandardMatrix::Bt2020 => YuvBias {
+                kr: 0.2627f32,
+                kb: 0.0593f32,
+            },
+            YuvStandardMatrix::Smpte240 => YuvBias {
+                kr: 0.087f32,
+                kb: 0.212f32,
+            },
+            YuvStandardMatrix::Bt470_6 => YuvBias {
+                kr: 0.2220f32,
+                kb: 0.0713f32,
+            },
+            YuvStandardMatrix::Identity => unreachable!(),
+        }
+    }
+}
+
+pub(crate) struct YuvPlanarImage<'a, T> {
+    pub(crate) y_plane: &'a [T],
+    pub(crate) y_stride: usize,
+    pub(crate) u_plane: &'a [T],
+    pub(crate) u_stride: usize,
+    pub(crate) v_plane: &'a [T],
+    pub(crate) v_stride: usize,
+    pub(crate) width: usize,
+    pub(crate) height: usize,
+}
+
+#[inline(always)]
+/// Saturating rounding shift right against bit depth
+fn qrshr<const PRECISION: i32, const BIT_DEPTH: usize>(val: i32) -> i32 {
+    let rounding: i32 = 1 << (PRECISION - 1);
+    let max_value: i32 = (1 << BIT_DEPTH) - 1;
+    ((val + rounding) >> PRECISION).clamp(0, max_value)
+}
+
+/// Converts Yuv 400 planar format 8 bit to Rgba 8 bit
+///
+/// # Arguments
+///
+/// * `image`: see [YuvGrayImage]
+/// * `rgba`: RGBA image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(crate) fn yuv400_to_rgba8(
+    image: YuvPlanarImage<u8>,
+    rgba: &mut [u8],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    yuv400_to_rgbx_impl::<u8, 4, 8>(image, rgba, range, matrix)
+}
+
+/// Converts Yuv 400 planar format 10 bit to Rgba 10 bit
+///
+/// Stride here is not supported as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvGrayImage]
+/// * `rgba`: RGBA image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(crate) fn yuv400_to_rgba10(
+    image: YuvPlanarImage<u16>,
+    rgba: &mut [u16],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    yuv400_to_rgbx_impl::<u16, 4, 10>(image, rgba, range, matrix)
+}
+
+/// Converts Yuv 400 planar format 12 bit to Rgba 12 bit
+///
+/// Stride here is not supported as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvGrayImage]
+/// * `rgba`: RGBA image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(crate) fn yuv400_to_rgba12(
+    image: YuvPlanarImage<u16>,
+    rgba: &mut [u16],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    yuv400_to_rgbx_impl::<u16, 4, 12>(image, rgba, range, matrix)
+}
+
+/// Converts Yuv 400 planar format to Rgba
+///
+/// Stride here is not supported as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvGrayImage]
+/// * `rgba`: RGBA image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+#[inline]
+fn yuv400_to_rgbx_impl<
+    V: Copy + AsPrimitive<i32> + 'static + Sized,
+    const CHANNELS: usize,
+    const BIT_DEPTH: usize,
+>(
+    image: YuvPlanarImage<V>,
+    rgba: &mut [V],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError>
+where
+    i32: AsPrimitive<V>,
+{
+    assert!(
+        CHANNELS == 3 || CHANNELS == 4,
+        "YUV 4:0:0 -> RGB is implemented only on 3 and 4 channels"
+    );
+    assert!(
+        (8..=16).contains(&BIT_DEPTH),
+        "Invalid bit depth is provided"
+    );
+    assert!(
+        if BIT_DEPTH > 8 {
+            size_of::<V>() == 2
+        } else {
+            size_of::<V>() == 1
+        },
+        "Unsupported bit depth and data type combination"
+    );
+    assert_ne!(
+        matrix,
+        YuvStandardMatrix::Identity,
+        "Identity matrix cannot be used on 4:0:0"
+    );
+
+    let y_plane = image.y_plane;
+    let y_stride = image.y_stride;
+    let height = image.height;
+    let width = image.width;
+
+    check_yuv_plane_preconditions(y_plane, PlaneDefinition::Y, y_stride, height)?;
+    check_rgb_preconditions(rgba, width * CHANNELS, height)?;
+
+    let rgba_stride = width * CHANNELS;
+
+    let max_value = (1 << BIT_DEPTH) - 1;
+
+    // If luma plane is in full range it can be just redistributed across the image
+    if range == YuvIntensityRange::Pc {
+        let y_iter = y_plane.chunks_exact(y_stride);
+        let rgb_iter = rgba.chunks_exact_mut(rgba_stride);
+
+        // All branches on generic const will be optimized out.
+        for (y_src, rgb) in y_iter.zip(rgb_iter) {
+            let rgb_chunks = rgb.chunks_exact_mut(CHANNELS);
+
+            for (y_src, rgb_dst) in y_src.iter().zip(rgb_chunks) {
+                let r = *y_src;
+                rgb_dst[0] = r;
+                rgb_dst[1] = r;
+                rgb_dst[2] = r;
+                if CHANNELS == 4 {
+                    rgb_dst[3] = max_value.as_();
+                }
+            }
+        }
+        return Ok(());
+    }
+
+    let range = range.get_yuv_range(BIT_DEPTH as u32);
+    let kr_kb = matrix.get_kr_kb();
+    const PRECISION: i32 = 11;
+
+    let inverse_transform = get_inverse_transform(
+        (1 << BIT_DEPTH) - 1,
+        range.range_y,
+        range.range_uv,
+        kr_kb.kr,
+        kr_kb.kb,
+        PRECISION as u32,
+    );
+    let y_coef = inverse_transform.y_coef;
+
+    let bias_y = range.bias_y as i32;
+
+    let y_iter = y_plane.chunks_exact(y_stride);
+    let rgb_iter = rgba.chunks_exact_mut(rgba_stride);
+
+    // All branches on generic const will be optimized out.
+    for (y_src, rgb) in y_iter.zip(rgb_iter) {
+        let rgb_chunks = rgb.chunks_exact_mut(CHANNELS);
+
+        for (y_src, rgb_dst) in y_src.iter().zip(rgb_chunks) {
+            let y_value = (y_src.as_() - bias_y) * y_coef;
+
+            let r = qrshr::<PRECISION, BIT_DEPTH>(y_value);
+            rgb_dst[0] = r.as_();
+            rgb_dst[1] = r.as_();
+            rgb_dst[2] = r.as_();
+            if CHANNELS == 4 {
+                rgb_dst[3] = max_value.as_();
+            }
+        }
+    }
+
+    Ok(())
+}
+
+/// Converts YUV420 8 bit-depth to Rgba 8 bit
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgb`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(crate) fn yuv420_to_rgba8(
+    image: YuvPlanarImage<u8>,
+    rgb: &mut [u8],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    yuv420_to_rgbx::<u8, 4, 8>(image, rgb, range, matrix)
+}
+
+/// Converts YUV420 10 bit-depth to Rgba 10 bit-depth
+///
+/// Stride here is not supported as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgb`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(crate) fn yuv420_to_rgba10(
+    image: YuvPlanarImage<u16>,
+    rgb: &mut [u16],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    yuv420_to_rgbx::<u16, 4, 10>(image, rgb, range, matrix)
+}
+
+/// Converts YUV420 12 bit-depth to Rgba 12 bit-depth
+///
+/// Stride here is not supported as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgb`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(crate) fn yuv420_to_rgba12(
+    image: YuvPlanarImage<u16>,
+    rgb: &mut [u16],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    yuv420_to_rgbx::<u16, 4, 12>(image, rgb, range, matrix)
+}
+
+#[inline]
+fn process_halved_chroma_row<
+    V: Copy + AsPrimitive<i32> + 'static + Sized,
+    const PRECISION: i32,
+    const CHANNELS: usize,
+    const BIT_DEPTH: usize,
+>(
+    image: YuvPlanarImage<V>,
+    rgba: &mut [V],
+    transform: &CbCrInverseTransform<i32>,
+    range: &YuvChromaRange,
+) where
+    i32: AsPrimitive<V>,
+{
+    let cr_coef = transform.cr_coef;
+    let cb_coef = transform.cb_coef;
+    let y_coef = transform.y_coef;
+    let g_coef_1 = transform.g_coeff_1;
+    let g_coef_2 = transform.g_coeff_2;
+
+    let max_value = (1 << BIT_DEPTH) - 1;
+
+    let bias_y = range.bias_y as i32;
+    let bias_uv = range.bias_uv as i32;
+    let y_iter = image.y_plane.chunks_exact(2);
+    let rgb_chunks = rgba.chunks_exact_mut(CHANNELS * 2);
+    for (((y_src, &u_src), &v_src), rgb_dst) in
+        y_iter.zip(image.u_plane).zip(image.v_plane).zip(rgb_chunks)
+    {
+        let y_value: i32 = (y_src[0].as_() - bias_y) * y_coef;
+        let cb_value: i32 = u_src.as_() - bias_uv;
+        let cr_value: i32 = v_src.as_() - bias_uv;
+
+        let r = qrshr::<PRECISION, BIT_DEPTH>(y_value + cr_coef * cr_value);
+        let b = qrshr::<PRECISION, BIT_DEPTH>(y_value + cb_coef * cb_value);
+        let g = qrshr::<PRECISION, BIT_DEPTH>(y_value - g_coef_1 * cr_value - g_coef_2 * cb_value);
+
+        if CHANNELS == 4 {
+            rgb_dst[0] = r.as_();
+            rgb_dst[1] = g.as_();
+            rgb_dst[2] = b.as_();
+            rgb_dst[3] = max_value.as_();
+        } else if CHANNELS == 3 {
+            rgb_dst[0] = r.as_();
+            rgb_dst[1] = g.as_();
+            rgb_dst[2] = b.as_();
+        } else {
+            unreachable!();
+        }
+
+        let y_value = (y_src[1].as_() - bias_y) * y_coef;
+
+        let r = qrshr::<PRECISION, BIT_DEPTH>(y_value + cr_coef * cr_value);
+        let b = qrshr::<PRECISION, BIT_DEPTH>(y_value + cb_coef * cb_value);
+        let g = qrshr::<PRECISION, BIT_DEPTH>(y_value - g_coef_1 * cr_value - g_coef_2 * cb_value);
+
+        if CHANNELS == 4 {
+            rgb_dst[4] = r.as_();
+            rgb_dst[5] = g.as_();
+            rgb_dst[6] = b.as_();
+            rgb_dst[7] = max_value.as_();
+        } else if CHANNELS == 3 {
+            rgb_dst[3] = r.as_();
+            rgb_dst[4] = g.as_();
+            rgb_dst[5] = b.as_();
+        } else {
+            unreachable!();
+        }
+    }
+
+    // Process remainder if width is odd.
+    if image.width & 1 != 0 {
+        let y_left = image.y_plane.chunks_exact(2).remainder();
+        let rgb_chunks = rgba
+            .chunks_exact_mut(CHANNELS * 2)
+            .into_remainder()
+            .chunks_exact_mut(CHANNELS);
+        let u_iter = image.u_plane.iter().rev();
+        let v_iter = image.v_plane.iter().rev();
+
+        for (((y_src, u_src), v_src), rgb_dst) in
+            y_left.iter().zip(u_iter).zip(v_iter).zip(rgb_chunks)
+        {
+            let y_value = (y_src.as_() - bias_y) * y_coef;
+            let cb_value = u_src.as_() - bias_uv;
+            let cr_value = v_src.as_() - bias_uv;
+
+            let r = qrshr::<PRECISION, BIT_DEPTH>(y_value + cr_coef * cr_value);
+            let b = qrshr::<PRECISION, BIT_DEPTH>(y_value + cb_coef * cb_value);
+            let g =
+                qrshr::<PRECISION, BIT_DEPTH>(y_value - g_coef_1 * cr_value - g_coef_2 * cb_value);
+
+            if CHANNELS == 4 {
+                rgb_dst[0] = r.as_();
+                rgb_dst[1] = g.as_();
+                rgb_dst[2] = b.as_();
+                rgb_dst[3] = max_value.as_();
+            } else if CHANNELS == 3 {
+                rgb_dst[0] = r.as_();
+                rgb_dst[1] = g.as_();
+                rgb_dst[2] = b.as_();
+            } else {
+                unreachable!();
+            }
+        }
+    }
+}
+
+/// Converts YUV420 to Rgba
+///
+/// Stride here is not supported as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgb`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+#[inline]
+fn yuv420_to_rgbx<
+    V: Copy + AsPrimitive<i32> + 'static + Sized,
+    const CHANNELS: usize,
+    const BIT_DEPTH: usize,
+>(
+    image: YuvPlanarImage<V>,
+    rgb: &mut [V],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError>
+where
+    i32: AsPrimitive<V>,
+{
+    assert!(
+        CHANNELS == 3 || CHANNELS == 4,
+        "YUV 4:2:0 -> RGB is implemented only on 3 and 4 channels"
+    );
+    assert!(
+        (8..=16).contains(&BIT_DEPTH),
+        "Invalid bit depth is provided"
+    );
+    assert!(
+        if BIT_DEPTH > 8 {
+            size_of::<V>() == 2
+        } else {
+            size_of::<V>() == 1
+        },
+        "Unsupported bit depth and data type combination"
+    );
+    assert_ne!(
+        matrix,
+        YuvStandardMatrix::Identity,
+        "Identity matrix cannot be used on 4:2:0"
+    );
+    let y_plane = image.y_plane;
+    let u_plane = image.u_plane;
+    let v_plane = image.v_plane;
+    let y_stride = image.y_stride;
+    let u_stride = image.u_stride;
+    let v_stride = image.v_stride;
+    let chroma_height = (image.height + 1) / 2;
+
+    check_yuv_plane_preconditions(y_plane, PlaneDefinition::Y, y_stride, image.height)?;
+    check_yuv_plane_preconditions(u_plane, PlaneDefinition::U, u_stride, chroma_height)?;
+    check_yuv_plane_preconditions(v_plane, PlaneDefinition::V, v_stride, chroma_height)?;
+
+    check_rgb_preconditions(rgb, image.width * CHANNELS, image.height)?;
+
+    const PRECISION: i32 = 11;
+
+    let range = range.get_yuv_range(BIT_DEPTH as u32);
+    let kr_kb = matrix.get_kr_kb();
+    let inverse_transform = get_inverse_transform(
+        (1 << BIT_DEPTH) - 1,
+        range.range_y,
+        range.range_uv,
+        kr_kb.kr,
+        kr_kb.kb,
+        PRECISION as u32,
+    );
+
+    let rgb_stride = image.width * CHANNELS;
+
+    let y_iter = y_plane.chunks_exact(y_stride * 2);
+    let rgb_iter = rgb.chunks_exact_mut(rgb_stride * 2);
+    let u_iter = u_plane.chunks_exact(u_stride);
+    let v_iter = v_plane.chunks_exact(v_stride);
+
+    /*
+       Sample 4x4 YUV420 planar image
+       start_y + 0:  Y00 Y01 Y02 Y03
+       start_y + 4:  Y04 Y05 Y06 Y07
+       start_y + 8:  Y08 Y09 Y10 Y11
+       start_y + 12: Y12 Y13 Y14 Y15
+       start_cb + 0: Cb00 Cb01
+       start_cb + 2: Cb02 Cb03
+       start_cr + 0: Cr00 Cr01
+       start_cr + 2: Cr02 Cr03
+
+       For 4 luma components (2x2 on rows and cols) there are 1 chroma Cb/Cr components.
+       Luma channel must have always exact size as RGB target layout, but chroma is not.
+
+       We're sectioning an image by pair of rows, then for each pair of luma and RGB row,
+       there is one chroma row.
+
+       As chroma is shrunk by factor of 2 then we're processing by pairs of RGB and luma,
+       for each RGB and luma pair there is one chroma component.
+
+       If image have odd width then luma channel must be exact, and we're replicating last
+       chroma component.
+
+       If image have odd height then luma channel is exact, and we're replicating last chroma rows.
+    */
+
+    // All branches on generic const will be optimized out.
+    for (((y_src, u_src), v_src), rgb) in y_iter.zip(u_iter).zip(v_iter).zip(rgb_iter) {
+        // Since we're processing two rows in one loop we need to re-slice once more
+        let y_iter = y_src.chunks_exact(y_stride);
+        let rgb_iter = rgb.chunks_exact_mut(rgb_stride);
+        for (y_src, rgba) in y_iter.zip(rgb_iter) {
+            let image = YuvPlanarImage {
+                y_plane: y_src,
+                y_stride: 0,
+                u_plane: u_src,
+                u_stride: 0,
+                v_plane: v_src,
+                v_stride: 0,
+                width: image.width,
+                height: image.height,
+            };
+            process_halved_chroma_row::<V, PRECISION, CHANNELS, BIT_DEPTH>(
+                image,
+                rgba,
+                &inverse_transform,
+                &range,
+            );
+        }
+    }
+
+    // Process remainder if height is odd
+
+    let y_iter = y_plane
+        .chunks_exact(y_stride * 2)
+        .remainder()
+        .chunks_exact(y_stride);
+    let rgb_iter = rgb.chunks_exact_mut(rgb_stride).rev();
+    let u_iter = u_plane.chunks_exact(u_stride).rev();
+    let v_iter = v_plane.chunks_exact(v_stride).rev();
+
+    for (((y_src, u_src), v_src), rgba) in y_iter.zip(u_iter).zip(v_iter).zip(rgb_iter) {
+        let image = YuvPlanarImage {
+            y_plane: y_src,
+            y_stride: 0,
+            u_plane: u_src,
+            u_stride: 0,
+            v_plane: v_src,
+            v_stride: 0,
+            width: image.width,
+            height: image.height,
+        };
+        process_halved_chroma_row::<V, PRECISION, CHANNELS, BIT_DEPTH>(
+            image,
+            rgba,
+            &inverse_transform,
+            &range,
+        );
+    }
+
+    Ok(())
+}
+
+/// Converts Yuv 422 8-bit planar format to Rgba 8-bit
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgb`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(crate) fn yuv422_to_rgba8(
+    image: YuvPlanarImage<u8>,
+    rgb: &mut [u8],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    yuv422_to_rgbx_impl::<u8, 4, 8>(image, rgb, range, matrix)
+}
+
+/// Converts Yuv 422 10-bit planar format to Rgba 10-bit
+///
+/// Stride here is not supported as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgb`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(crate) fn yuv422_to_rgba10(
+    image: YuvPlanarImage<u16>,
+    rgb: &mut [u16],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    yuv422_to_rgbx_impl::<u16, 4, 10>(image, rgb, range, matrix)
+}
+
+/// Converts Yuv 422 12-bit planar format to Rgba 12-bit
+///
+/// Stride here is not supported as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgb`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(crate) fn yuv422_to_rgba12(
+    image: YuvPlanarImage<u16>,
+    rgb: &mut [u16],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    yuv422_to_rgbx_impl::<u16, 4, 12>(image, rgb, range, matrix)
+}
+
+/// Converts Yuv 422 planar format to Rgba
+///
+/// Stride here is not supports u16 as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgb`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+fn yuv422_to_rgbx_impl<
+    V: Copy + AsPrimitive<i32> + 'static + Sized,
+    const CHANNELS: usize,
+    const BIT_DEPTH: usize,
+>(
+    image: YuvPlanarImage<V>,
+    rgb: &mut [V],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError>
+where
+    i32: AsPrimitive<V>,
+{
+    assert!(
+        CHANNELS == 3 || CHANNELS == 4,
+        "YUV 4:2:2 -> RGB is implemented only on 3 and 4 channels"
+    );
+    assert!(
+        (8..=16).contains(&BIT_DEPTH),
+        "Invalid bit depth is provided"
+    );
+    assert!(
+        if BIT_DEPTH > 8 {
+            size_of::<V>() == 2
+        } else {
+            size_of::<V>() == 1
+        },
+        "Unsupported bit depth and data type combination"
+    );
+    assert_ne!(
+        matrix,
+        YuvStandardMatrix::Identity,
+        "Identity matrix cannot be used on 4:2:2"
+    );
+    let y_plane = image.y_plane;
+    let u_plane = image.u_plane;
+    let v_plane = image.v_plane;
+    let y_stride = image.y_stride;
+    let u_stride = image.u_stride;
+    let v_stride = image.v_stride;
+    let width = image.width;
+
+    check_yuv_plane_preconditions(y_plane, PlaneDefinition::Y, y_stride, image.height)?;
+    check_yuv_plane_preconditions(u_plane, PlaneDefinition::U, u_stride, image.height)?;
+    check_yuv_plane_preconditions(v_plane, PlaneDefinition::V, v_stride, image.height)?;
+
+    check_rgb_preconditions(rgb, image.width * CHANNELS, image.height)?;
+
+    let range = range.get_yuv_range(BIT_DEPTH as u32);
+    let kr_kb = matrix.get_kr_kb();
+    const PRECISION: i32 = 11;
+
+    let inverse_transform = get_inverse_transform(
+        (1 << BIT_DEPTH) - 1,
+        range.range_y,
+        range.range_uv,
+        kr_kb.kr,
+        kr_kb.kb,
+        PRECISION as u32,
+    );
+
+    /*
+       Sample 4x4 YUV422 planar image
+       start_y + 0:  Y00 Y01 Y02 Y03
+       start_y + 4:  Y04 Y05 Y06 Y07
+       start_y + 8:  Y08 Y09 Y10 Y11
+       start_y + 12: Y12 Y13 Y14 Y15
+       start_cb + 0: Cb00 Cb01
+       start_cb + 2: Cb02 Cb03
+       start_cb + 4: Cb04 Cb05
+       start_cb + 6: Cb06 Cb07
+       start_cr + 0: Cr00 Cr01
+       start_cr + 2: Cr02 Cr03
+       start_cr + 4: Cr04 Cr05
+       start_cr + 6: Cr06 Cr07
+
+       For 2 luma components there are 1 chroma Cb/Cr components.
+       Luma channel must have always exact size as RGB target layout, but chroma is not.
+
+       As chroma is shrunk by factor of 2 then we're processing by pairs of RGB and luma,
+       for each RGB and luma pair there is one chroma component.
+
+       If image have odd width then luma channel must be exact, and we're replicating last
+       chroma component.
+    */
+
+    let rgb_stride = width * CHANNELS;
+
+    let y_iter = y_plane.chunks_exact(y_stride);
+    let rgb_iter = rgb.chunks_exact_mut(rgb_stride);
+    let u_iter = u_plane.chunks_exact(u_stride);
+    let v_iter = v_plane.chunks_exact(v_stride);
+
+    // All branches on generic const will be optimized out.
+    for (((y_src, u_src), v_src), rgba) in y_iter.zip(u_iter).zip(v_iter).zip(rgb_iter) {
+        let image = YuvPlanarImage {
+            y_plane: y_src,
+            y_stride: 0,
+            u_plane: u_src,
+            u_stride: 0,
+            v_plane: v_src,
+            v_stride: 0,
+            width: image.width,
+            height: image.height,
+        };
+        process_halved_chroma_row::<V, PRECISION, CHANNELS, BIT_DEPTH>(
+            image,
+            rgba,
+            &inverse_transform,
+            &range,
+        );
+    }
+
+    Ok(())
+}
+
+/// Converts Yuv 444 planar format 8 bit-depth to Rgba 8 bit
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgba`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(crate) fn yuv444_to_rgba8(
+    image: YuvPlanarImage<u8>,
+    rgba: &mut [u8],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    if matrix == YuvStandardMatrix::Identity {
+        gbr_to_rgba8(image, rgba, range)
+    } else {
+        yuv444_to_rgbx_impl::<u8, 4, 8>(image, rgba, range, matrix)
+    }
+}
+
+/// Converts Yuv 444 planar format 10 bit-depth to Rgba 10 bit
+///
+/// Stride here is not supports u16 as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgba`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(super) fn yuv444_to_rgba10(
+    image: YuvPlanarImage<u16>,
+    rgba: &mut [u16],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    if matrix == YuvStandardMatrix::Identity {
+        gbr_to_rgba10(image, rgba, range)
+    } else {
+        yuv444_to_rgbx_impl::<u16, 4, 10>(image, rgba, range, matrix)
+    }
+}
+
+/// Converts Yuv 444 planar format 12 bit-depth to Rgba 12 bit
+///
+/// Stride here is not supports u16 as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgba`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+pub(super) fn yuv444_to_rgba12(
+    image: YuvPlanarImage<u16>,
+    rgba: &mut [u16],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError> {
+    if matrix == YuvStandardMatrix::Identity {
+        gbr_to_rgba12(image, rgba, range)
+    } else {
+        yuv444_to_rgbx_impl::<u16, 4, 12>(image, rgba, range, matrix)
+    }
+}
+
+/// Converts Yuv 444 planar format to Rgba
+///
+/// Stride here is not supports u16 as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgba`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+/// * `matrix`: see [YuvStandardMatrix]
+///
+///
+#[inline]
+fn yuv444_to_rgbx_impl<
+    V: Copy + AsPrimitive<i32> + 'static + Sized,
+    const CHANNELS: usize,
+    const BIT_DEPTH: usize,
+>(
+    image: YuvPlanarImage<V>,
+    rgba: &mut [V],
+    range: YuvIntensityRange,
+    matrix: YuvStandardMatrix,
+) -> Result<(), ImageError>
+where
+    i32: AsPrimitive<V>,
+{
+    assert!(
+        CHANNELS == 3 || CHANNELS == 4,
+        "YUV 4:4:4 -> RGB is implemented only on 3 and 4 channels"
+    );
+    assert!(
+        (8..=16).contains(&BIT_DEPTH),
+        "Invalid bit depth is provided"
+    );
+    assert!(
+        if BIT_DEPTH > 8 {
+            size_of::<V>() == 2
+        } else {
+            size_of::<V>() == 1
+        },
+        "Unsupported bit depth and data type combination"
+    );
+
+    let y_plane = image.y_plane;
+    let u_plane = image.u_plane;
+    let v_plane = image.v_plane;
+    let y_stride = image.y_stride;
+    let u_stride = image.u_stride;
+    let v_stride = image.v_stride;
+    let height = image.height;
+    let width = image.width;
+
+    check_yuv_plane_preconditions(y_plane, PlaneDefinition::Y, y_stride, height)?;
+    check_yuv_plane_preconditions(u_plane, PlaneDefinition::U, u_stride, height)?;
+    check_yuv_plane_preconditions(v_plane, PlaneDefinition::V, v_stride, height)?;
+
+    check_rgb_preconditions(rgba, image.width * CHANNELS, height)?;
+
+    let range = range.get_yuv_range(BIT_DEPTH as u32);
+    let kr_kb = matrix.get_kr_kb();
+    const PRECISION: i32 = 11;
+
+    let inverse_transform = get_inverse_transform(
+        (1 << BIT_DEPTH) - 1,
+        range.range_y,
+        range.range_uv,
+        kr_kb.kr,
+        kr_kb.kb,
+        PRECISION as u32,
+    );
+    let cr_coef = inverse_transform.cr_coef;
+    let cb_coef = inverse_transform.cb_coef;
+    let y_coef = inverse_transform.y_coef;
+    let g_coef_1 = inverse_transform.g_coeff_1;
+    let g_coef_2 = inverse_transform.g_coeff_2;
+
+    let bias_y = range.bias_y as i32;
+    let bias_uv = range.bias_uv as i32;
+
+    let max_value = (1 << BIT_DEPTH) - 1;
+
+    let rgb_stride = width * CHANNELS;
+
+    let y_iter = y_plane.chunks_exact(y_stride);
+    let rgb_iter = rgba.chunks_exact_mut(rgb_stride);
+    let u_iter = u_plane.chunks_exact(u_stride);
+    let v_iter = v_plane.chunks_exact(v_stride);
+
+    // All branches on generic const will be optimized out.
+    for (((y_src, u_src), v_src), rgb) in y_iter.zip(u_iter).zip(v_iter).zip(rgb_iter) {
+        let rgb_chunks = rgb.chunks_exact_mut(CHANNELS);
+
+        for (((y_src, u_src), v_src), rgb_dst) in y_src.iter().zip(u_src).zip(v_src).zip(rgb_chunks)
+        {
+            let y_value = (y_src.as_() - bias_y) * y_coef;
+            let cb_value = u_src.as_() - bias_uv;
+            let cr_value = v_src.as_() - bias_uv;
+
+            let r = qrshr::<PRECISION, BIT_DEPTH>(y_value + cr_coef * cr_value);
+            let b = qrshr::<PRECISION, BIT_DEPTH>(y_value + cb_coef * cb_value);
+            let g =
+                qrshr::<PRECISION, BIT_DEPTH>(y_value - g_coef_1 * cr_value - g_coef_2 * cb_value);
+
+            if CHANNELS == 4 {
+                rgb_dst[0] = r.as_();
+                rgb_dst[1] = g.as_();
+                rgb_dst[2] = b.as_();
+                rgb_dst[3] = max_value.as_();
+            } else if CHANNELS == 3 {
+                rgb_dst[0] = r.as_();
+                rgb_dst[1] = g.as_();
+                rgb_dst[2] = b.as_();
+            } else {
+                unreachable!();
+            }
+        }
+    }
+
+    Ok(())
+}
+
+/// Converts Gbr 8 bit planar format to Rgba 8 bit-depth
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgb`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+///
+///
+fn gbr_to_rgba8(
+    image: YuvPlanarImage<u8>,
+    rgb: &mut [u8],
+    range: YuvIntensityRange,
+) -> Result<(), ImageError> {
+    gbr_to_rgbx_impl::<u8, 4, 8>(image, rgb, range)
+}
+
+/// Converts Gbr 10 bit planar format to Rgba 10 bit-depth
+///
+/// Stride here is not supported as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgba`: RGBx image layout
+/// * `range`: see [YuvIntensityRange]
+///
+///
+fn gbr_to_rgba10(
+    image: YuvPlanarImage<u16>,
+    rgba: &mut [u16],
+    range: YuvIntensityRange,
+) -> Result<(), ImageError> {
+    gbr_to_rgbx_impl::<u16, 4, 10>(image, rgba, range)
+}
+
+/// Converts Gbr 12 bit planar format to Rgba 12 bit-depth
+///
+/// Stride here is not supported as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgba`: RGBx image layout
+/// * `range`: see [YuvIntensityRange]
+///
+///
+fn gbr_to_rgba12(
+    image: YuvPlanarImage<u16>,
+    rgba: &mut [u16],
+    range: YuvIntensityRange,
+) -> Result<(), ImageError> {
+    gbr_to_rgbx_impl::<u16, 4, 12>(image, rgba, range)
+}
+
+/// Converts Gbr planar format to Rgba
+///
+/// Stride here is not supported as it can be in passed from FFI.
+///
+/// # Arguments
+///
+/// * `image`: see [YuvPlanarImage]
+/// * `rgb`: RGB image layout
+/// * `range`: see [YuvIntensityRange]
+///
+///
+#[inline]
+fn gbr_to_rgbx_impl<
+    V: Copy + AsPrimitive<i32> + 'static + Sized,
+    const CHANNELS: usize,
+    const BIT_DEPTH: usize,
+>(
+    image: YuvPlanarImage<V>,
+    rgba: &mut [V],
+    yuv_range: YuvIntensityRange,
+) -> Result<(), ImageError>
+where
+    i32: AsPrimitive<V>,
+{
+    assert!(
+        CHANNELS == 3 || CHANNELS == 4,
+        "GBR -> RGB is implemented only on 3 and 4 channels"
+    );
+    assert!(
+        (8..=16).contains(&BIT_DEPTH),
+        "Invalid bit depth is provided"
+    );
+    assert!(
+        if BIT_DEPTH > 8 {
+            size_of::<V>() == 2
+        } else {
+            size_of::<V>() == 1
+        },
+        "Unsupported bit depth and data type combination"
+    );
+    let y_plane = image.y_plane;
+    let u_plane = image.u_plane;
+    let v_plane = image.v_plane;
+    let y_stride = image.y_stride;
+    let u_stride = image.u_stride;
+    let v_stride = image.v_stride;
+    let height = image.height;
+    let width = image.width;
+
+    check_yuv_plane_preconditions(y_plane, PlaneDefinition::Y, y_stride, height)?;
+    check_yuv_plane_preconditions(u_plane, PlaneDefinition::U, u_stride, height)?;
+    check_yuv_plane_preconditions(v_plane, PlaneDefinition::V, v_stride, height)?;
+
+    check_rgb_preconditions(rgba, width * CHANNELS, height)?;
+
+    let max_value = (1 << BIT_DEPTH) - 1;
+
+    let rgb_stride = width * CHANNELS;
+
+    let y_iter = y_plane.chunks_exact(y_stride);
+    let rgb_iter = rgba.chunks_exact_mut(rgb_stride);
+    let u_iter = u_plane.chunks_exact(u_stride);
+    let v_iter = v_plane.chunks_exact(v_stride);
+
+    match yuv_range {
+        YuvIntensityRange::Tv => {
+            const PRECISION: i32 = 11;
+            // All channels on identity should use Y range
+            let range = yuv_range.get_yuv_range(BIT_DEPTH as u32);
+            let range_rgba = (1 << BIT_DEPTH) - 1;
+            let y_coef =
+                ((range_rgba as f32 / range.range_y as f32) * (1 << PRECISION) as f32) as i32;
+            let y_bias = range.bias_y as i32;
+
+            for (((y_src, u_src), v_src), rgb) in y_iter.zip(u_iter).zip(v_iter).zip(rgb_iter) {
+                let rgb_chunks = rgb.chunks_exact_mut(CHANNELS);
+
+                for (((&y_src, &u_src), &v_src), rgb_dst) in
+                    y_src.iter().zip(u_src).zip(v_src).zip(rgb_chunks)
+                {
+                    rgb_dst[0] =
+                        qrshr::<PRECISION, BIT_DEPTH>((v_src.as_() - y_bias) * y_coef).as_();
+                    rgb_dst[1] =
+                        qrshr::<PRECISION, BIT_DEPTH>((y_src.as_() - y_bias) * y_coef).as_();
+                    rgb_dst[2] =
+                        qrshr::<PRECISION, BIT_DEPTH>((u_src.as_() - y_bias) * y_coef).as_();
+                    if CHANNELS == 4 {
+                        rgb_dst[3] = max_value.as_();
+                    }
+                }
+            }
+        }
+        YuvIntensityRange::Pc => {
+            for (((y_src, u_src), v_src), rgb) in y_iter.zip(u_iter).zip(v_iter).zip(rgb_iter) {
+                let rgb_chunks = rgb.chunks_exact_mut(CHANNELS);
+
+                for (((&y_src, &u_src), &v_src), rgb_dst) in
+                    y_src.iter().zip(u_src).zip(v_src).zip(rgb_chunks)
+                {
+                    rgb_dst[0] = v_src;
+                    rgb_dst[1] = y_src;
+                    rgb_dst[2] = u_src;
+                    if CHANNELS == 4 {
+                        rgb_dst[3] = max_value.as_();
+                    }
+                }
+            }
+        }
+    }
+
+    Ok(())
+}


Test patch: