Skip to content

Commit

Permalink
AVIF: Add a decoder based on mp4parser and dav1d.
Browse files Browse the repository at this point in the history
  • Loading branch information
linkmauve committed Jan 18, 2021
1 parent 19637e5 commit b77151a
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 1 deletion.
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ scoped_threadpool = { version = "0.1", optional = true }
tiff = { version = "0.6.0", optional = true }
ravif = { version = "0.6.0", optional = true }
rgb = { version = "0.8.25", optional = true }
#mp4parse = { version = "0.11.4", git = "https://github.com/mozilla/mp4parse-rust", path = "mp4parse", optional = true }
mp4parse = { version = "0.11.4", path = "../mp4parse/mp4parse", optional = true }
dav1d = { version = "0.5.2", optional = true, path = "../dav1d" }
dcv-color-primitives = { version = "0.1.16", optional = true }
color_quant = "1.1"

[dev-dependencies]
Expand All @@ -59,7 +63,7 @@ farbfeld = []
jpeg_rayon = ["jpeg/rayon"]
# Non-default, enables avif encoding.
# Requires latest stable Rust.
avif = ["ravif", "rgb"]
avif = ["mp4parse", "dav1d", "dcv-color-primitives", "ravif", "rgb"]

# Build some inline benchmarks. Useful only during development.
# Requires rustc nightly for feature test.
Expand Down
160 changes: 160 additions & 0 deletions src/codecs/avif/decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//! Decoding of AVIF images.
///
/// The [AVIF] specification defines an image derivative of the AV1 bitstream, an open video codec.
///
/// [AVIF]: https://aomediacodec.github.io/av1-avif/
use std::convert::TryFrom;
use std::io::{self, Cursor, Read};
use std::marker::PhantomData;
use std::mem;

use crate::{ColorType, ImageResult, ImageDecoder, ImageFormat, ImageError};
use crate::error::DecodingError;

use mp4parse::read_avif;
use dav1d::{PlanarImageComponent, PixelLayout};
use dcv_color_primitives as dcp;

impl From<mp4parse::Error> for ImageError {
fn from(err: mp4parse::Error) -> ImageError {
ImageError::Decoding(DecodingError::new(ImageFormat::Avif.into(), err))
}
}

impl From<dav1d::Error> for ImageError {
fn from(err: dav1d::Error) -> ImageError {
ImageError::Decoding(DecodingError::new(ImageFormat::Avif.into(), err))
}
}

impl From<dcp::ErrorKind> for ImageError {
fn from(err: dcp::ErrorKind) -> ImageError {
ImageError::Decoding(DecodingError::new(ImageFormat::Avif.into(), err))
}
}

/// AVIF Decoder.
///
/// Reads one image into the chosen input.
pub struct AvifDecoder<R> {
inner: PhantomData<R>,
picture: dav1d::Picture,
alpha_picture: Option<dav1d::Picture>,
}

impl<R: Read> AvifDecoder<R> {
/// Create a new decoder that reads its input from `r`.
pub fn new(mut r: R) -> ImageResult<Self> {
let ctx = read_avif(&mut r)?;
let mut primary_decoder = dav1d::Decoder::new();
primary_decoder.send_data(ctx.primary_item(), None, None, None)?;
let picture = primary_decoder.get_picture()?;
let alpha_picture = if let Some(alpha_item) = ctx.alpha_item() {
let mut alpha_decoder = dav1d::Decoder::new();
alpha_decoder.send_data(alpha_item, None, None, None)?;
Some(alpha_decoder.get_picture()?)
} else {
None
};
assert_eq!(picture.bit_depth(), 8);
Ok(AvifDecoder {
inner: PhantomData,
picture,
alpha_picture,
})
}
}

/// Wrapper struct around a `Cursor<Vec<u8>>`
pub struct AvifReader<R>(Cursor<Vec<u8>>, PhantomData<R>);
impl<R> Read for AvifReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
if self.0.position() == 0 && buf.is_empty() {
mem::swap(buf, self.0.get_mut());
Ok(buf.len())
} else {
self.0.read_to_end(buf)
}
}
}

impl<'a, R: 'a + Read> ImageDecoder<'a> for AvifDecoder<R> {
type Reader = AvifReader<R>;

fn dimensions(&self) -> (u32, u32) {
(self.picture.width(), self.picture.height())
}

fn color_type(&self) -> ColorType {
ColorType::Bgra8
}

fn into_reader(self) -> ImageResult<Self::Reader> {
let plane = self.picture.plane(PlanarImageComponent::Y);
Ok(AvifReader(Cursor::new(plane.as_ref().to_vec()), PhantomData))
}

fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));

dcp::initialize();

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,
PixelLayout::Unknown => panic!("Unknown pixel layout"),
};
let src_format = dcp::ImageFormat {
pixel_format,
color_space: dcp::ColorSpace::Bt601,
num_planes: 3,
};
let dst_format = dcp::ImageFormat {
pixel_format: dcp::PixelFormat::Bgra,
color_space: dcp::ColorSpace::Lrgb,
num_planes: 1,
};
let (width, height) = self.dimensions();
let planes = &[
self.picture.plane(PlanarImageComponent::Y),
self.picture.plane(PlanarImageComponent::Y),
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,
self.picture.stride(PlanarImageComponent::U) as usize,
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)?;
} else {
let plane = self.picture.plane(PlanarImageComponent::Y);
buf.copy_from_slice(plane.as_ref());
}

if let Some(picture) = self.alpha_picture {
assert_eq!(picture.pixel_layout(), PixelLayout::I400);
let stride = picture.stride(PlanarImageComponent::Y) as usize;
let plane = picture.plane(PlanarImageComponent::Y);
let width = picture.width();
for (buf, slice) in Iterator::zip(buf.chunks_exact_mut(width as usize * 4), plane.as_ref().chunks_exact(stride)) {
for i in 0..width as usize {
buf[3 + i * 4] = slice[i];
}
}
}

Ok(())
}
}
2 changes: 2 additions & 0 deletions src/codecs/avif/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
///
/// [AVIF]: https://aomediacodec.github.io/av1-avif/
pub use self::decoder::AvifDecoder;
pub use self::encoder::AvifEncoder;

mod decoder;
mod encoder;

0 comments on commit b77151a

Please sign in to comment.