Skip to content

Commit

Permalink
Merge pull request #1398 from linkmauve/avif-decoder
Browse files Browse the repository at this point in the history
Add an AVIF decoder
  • Loading branch information
HeroicKatora authored Jan 28, 2021
2 parents 3848dc8 + d63ad64 commit 80bfbb6
Show file tree
Hide file tree
Showing 7 changed files with 386 additions and 201 deletions.
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ 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.5", optional = true }
dav1d = { version = "0.6.0", optional = true }
dcv-color-primitives = { version = "0.1.16", optional = true }
color_quant = "1.1"

[dev-dependencies]
Expand All @@ -43,6 +46,7 @@ quickcheck = "0.9"
criterion = "0.3"

[features]
# TODO: Add "avif" to this list while preparing for 0.24.0
default = ["gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon"]

ico = ["bmp", "png"]
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ All image processing functions provided operate on types that implement the `Gen
| ICO | Yes | Yes |
| TIFF | Baseline(no fax support) + LZW + PackBits | RGB(8), RGBA(8), Gray(8) |
| WebP | Lossy(Luma channel only) | No |
| AVIF | No | Lossy |
| AVIF | Only 8-bit | Lossy |
| PNM | PBM, PGM, PPM, standard PAM | Yes |
| DDS | DXT1, DXT3, DXT5 | No |
| TGA | Yes | RGB(8), RGBA(8), BGR(8), BGRA(8), Gray(8), GrayA(8) |
Expand Down
163 changes: 163 additions & 0 deletions src/codecs/avif/decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
//! 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::error::Error;
use std::io::{self, Cursor, Read};
use std::marker::PhantomData;
use std::mem;

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

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

fn error_map<E: Into<Box<dyn Error + Send + Sync>>>(err: E) -> 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).map_err(error_map)?;
let mut primary_decoder = dav1d::Decoder::new();
primary_decoder
.send_data(ctx.primary_item(), None, None, None)
.map_err(error_map)?;
let picture = primary_decoder.get_picture().map_err(error_map)?;
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)
.map_err(error_map)?;
Some(alpha_decoder.get_picture().map_err(error_map)?)
} 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::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,
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,
)
.map_err(error_map)?;
} 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(())
}
}
Loading

0 comments on commit 80bfbb6

Please sign in to comment.