diff options
Diffstat (limited to 'vendor/image/src/codecs/avif')
-rw-r--r-- | vendor/image/src/codecs/avif/decoder.rs | 177 | ||||
-rw-r--r-- | vendor/image/src/codecs/avif/encoder.rs | 274 | ||||
-rw-r--r-- | vendor/image/src/codecs/avif/mod.rs | 14 |
3 files changed, 465 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/avif/decoder.rs b/vendor/image/src/codecs/avif/decoder.rs new file mode 100644 index 0000000..acba4f8 --- /dev/null +++ b/vendor/image/src/codecs/avif/decoder.rs @@ -0,0 +1,177 @@ +//! 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, ParseStrictness}; + +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>, + icc_profile: Option<Vec<u8>>, +} + +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, ParseStrictness::Normal).map_err(error_map)?; + let coded = ctx.primary_item_coded_data().unwrap_or_default(); + + let mut primary_decoder = dav1d::Decoder::new(); + primary_decoder + .send_data(coded, None, None, None) + .map_err(error_map)?; + let picture = primary_decoder.get_picture().map_err(error_map)?; + let alpha_item = ctx.alpha_item_coded_data().unwrap_or_default(); + let alpha_picture = if !alpha_item.is_empty() { + 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 + }; + let icc_profile = ctx + .icc_colour_information() + .map(|x| x.ok().unwrap_or_default()) + .map(|x| x.to_vec()); + + assert_eq!(picture.bit_depth(), 8); + Ok(AvifDecoder { + inner: PhantomData, + picture, + alpha_picture, + icc_profile, + }) + } +} + +/// 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::Rgba8 + } + + fn icc_profile(&mut self) -> Option<Vec<u8>> { + self.icc_profile.clone() + } + + 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::Rgba, + 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(()) + } +} diff --git a/vendor/image/src/codecs/avif/encoder.rs b/vendor/image/src/codecs/avif/encoder.rs new file mode 100644 index 0000000..7484ff1 --- /dev/null +++ b/vendor/image/src/codecs/avif/encoder.rs @@ -0,0 +1,274 @@ +//! Encoding 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::borrow::Cow; +use std::cmp::min; +use std::io::Write; + +use crate::buffer::ConvertBuffer; +use crate::color::{FromColor, Luma, LumaA, Rgb, Rgba}; +use crate::error::{ + EncodingError, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, +}; +use crate::{ColorType, ImageBuffer, ImageEncoder, ImageFormat, Pixel}; +use crate::{ImageError, ImageResult}; + +use bytemuck::{try_cast_slice, try_cast_slice_mut, Pod, PodCastError}; +use num_traits::Zero; +use ravif::{Encoder, Img, RGB8, RGBA8}; +use rgb::AsPixels; + +/// AVIF Encoder. +/// +/// Writes one image into the chosen output. +pub struct AvifEncoder<W> { + inner: W, + encoder: Encoder, +} + +/// An enumeration over supported AVIF color spaces +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum ColorSpace { + /// sRGB colorspace + Srgb, + /// BT.709 colorspace + Bt709, +} + +impl ColorSpace { + fn to_ravif(self) -> ravif::ColorSpace { + match self { + Self::Srgb => ravif::ColorSpace::RGB, + Self::Bt709 => ravif::ColorSpace::YCbCr, + } + } +} + +enum RgbColor<'buf> { + Rgb8(Img<&'buf [RGB8]>), + Rgba8(Img<&'buf [RGBA8]>), +} + +impl<W: Write> AvifEncoder<W> { + /// Create a new encoder that writes its output to `w`. + pub fn new(w: W) -> Self { + AvifEncoder::new_with_speed_quality(w, 4, 80) // `cavif` uses these defaults + } + + /// Create a new encoder with specified speed and quality, that writes its output to `w`. + /// `speed` accepts a value in the range 0-10, where 0 is the slowest and 10 is the fastest. + /// `quality` accepts a value in the range 0-100, where 0 is the worst and 100 is the best. + pub fn new_with_speed_quality(w: W, speed: u8, quality: u8) -> Self { + // Clamp quality and speed to range + let quality = min(quality, 100); + let speed = min(speed, 10); + + let encoder = Encoder::new() + .with_quality(f32::from(quality)) + .with_alpha_quality(f32::from(quality)) + .with_speed(speed); + + AvifEncoder { inner: w, encoder } + } + + /// Encode with the specified `color_space`. + pub fn with_colorspace(mut self, color_space: ColorSpace) -> Self { + self.encoder = self + .encoder + .with_internal_color_space(color_space.to_ravif()); + self + } + + /// Configures `rayon` thread pool size. + /// The default `None` is to use all threads in the default `rayon` thread pool. + pub fn with_num_threads(mut self, num_threads: Option<usize>) -> Self { + self.encoder = self.encoder.with_num_threads(num_threads); + self + } +} + +impl<W: Write> ImageEncoder for AvifEncoder<W> { + /// Encode image data with the indicated color type. + /// + /// The encoder currently requires all data to be RGBA8, it will be converted internally if + /// necessary. When data is suitably aligned, i.e. u16 channels to two bytes, then the + /// conversion may be more efficient. + fn write_image( + mut self, + data: &[u8], + width: u32, + height: u32, + color: ColorType, + ) -> ImageResult<()> { + self.set_color(color); + // `ravif` needs strongly typed data so let's convert. We can either use a temporarily + // owned version in our own buffer or zero-copy if possible by using the input buffer. + // This requires going through `rgb`. + let mut fallback = vec![]; // This vector is used if we need to do a color conversion. + let result = match Self::encode_as_img(&mut fallback, data, width, height, color)? { + RgbColor::Rgb8(buffer) => self.encoder.encode_rgb(buffer), + RgbColor::Rgba8(buffer) => self.encoder.encode_rgba(buffer), + }; + let data = result.map_err(|err| { + ImageError::Encoding(EncodingError::new(ImageFormat::Avif.into(), err)) + })?; + self.inner.write_all(&data.avif_file)?; + Ok(()) + } +} + +impl<W: Write> AvifEncoder<W> { + // Does not currently do anything. Mirrors behaviour of old config function. + fn set_color(&mut self, _color: ColorType) { + // self.config.color_space = ColorSpace::RGB; + } + + fn encode_as_img<'buf>( + fallback: &'buf mut Vec<u8>, + data: &'buf [u8], + width: u32, + height: u32, + color: ColorType, + ) -> ImageResult<RgbColor<'buf>> { + // Error wrapping utility for color dependent buffer dimensions. + fn try_from_raw<P: Pixel + 'static>( + data: &[P::Subpixel], + width: u32, + height: u32, + ) -> ImageResult<ImageBuffer<P, &[P::Subpixel]>> { + ImageBuffer::from_raw(width, height, data).ok_or_else(|| { + ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + )) + }) + } + + // Convert to target color type using few buffer allocations. + fn convert_into<'buf, P>( + buf: &'buf mut Vec<u8>, + image: ImageBuffer<P, &[P::Subpixel]>, + ) -> Img<&'buf [RGBA8]> + where + P: Pixel + 'static, + Rgba<u8>: FromColor<P>, + { + let (width, height) = image.dimensions(); + // TODO: conversion re-using the target buffer? + let image: ImageBuffer<Rgba<u8>, _> = image.convert(); + *buf = image.into_raw(); + Img::new(buf.as_pixels(), width as usize, height as usize) + } + + // Cast the input slice using few buffer allocations if possible. + // In particular try not to allocate if the caller did the infallible reverse. + fn cast_buffer<Channel>(buf: &[u8]) -> ImageResult<Cow<[Channel]>> + where + Channel: Pod + Zero, + { + match try_cast_slice(buf) { + Ok(slice) => Ok(Cow::Borrowed(slice)), + Err(PodCastError::OutputSliceWouldHaveSlop) => Err(ImageError::Parameter( + ParameterError::from_kind(ParameterErrorKind::DimensionMismatch), + )), + Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) => { + // Sad, but let's allocate. + // bytemuck checks alignment _before_ slop but size mismatch before this.. + if buf.len() % std::mem::size_of::<Channel>() != 0 { + Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))) + } else { + let len = buf.len() / std::mem::size_of::<Channel>(); + let mut data = vec![Channel::zero(); len]; + let view = try_cast_slice_mut::<_, u8>(data.as_mut_slice()).unwrap(); + view.copy_from_slice(buf); + Ok(Cow::Owned(data)) + } + } + Err(err) => { + // Are you trying to encode a ZST?? + Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic(format!("{:?}", err)), + ))) + } + } + } + + match color { + ColorType::Rgb8 => { + // ravif doesn't do any checks but has some asserts, so we do the checks. + let img = try_from_raw::<Rgb<u8>>(data, width, height)?; + // Now, internally ravif uses u32 but it takes usize. We could do some checked + // conversion but instead we use that a non-empty image must be addressable. + if img.pixels().len() == 0 { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))); + } + + Ok(RgbColor::Rgb8(Img::new( + rgb::AsPixels::as_pixels(data), + width as usize, + height as usize, + ))) + } + ColorType::Rgba8 => { + // ravif doesn't do any checks but has some asserts, so we do the checks. + let img = try_from_raw::<Rgba<u8>>(data, width, height)?; + // Now, internally ravif uses u32 but it takes usize. We could do some checked + // conversion but instead we use that a non-empty image must be addressable. + if img.pixels().len() == 0 { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + ))); + } + + Ok(RgbColor::Rgba8(Img::new( + rgb::AsPixels::as_pixels(data), + width as usize, + height as usize, + ))) + } + // we need a separate buffer.. + ColorType::L8 => { + let image = try_from_raw::<Luma<u8>>(data, width, height)?; + Ok(RgbColor::Rgba8(convert_into(fallback, image))) + } + ColorType::La8 => { + let image = try_from_raw::<LumaA<u8>>(data, width, height)?; + Ok(RgbColor::Rgba8(convert_into(fallback, image))) + } + // we need to really convert data.. + ColorType::L16 => { + let buffer = cast_buffer(data)?; + let image = try_from_raw::<Luma<u16>>(&buffer, width, height)?; + Ok(RgbColor::Rgba8(convert_into(fallback, image))) + } + ColorType::La16 => { + let buffer = cast_buffer(data)?; + let image = try_from_raw::<LumaA<u16>>(&buffer, width, height)?; + Ok(RgbColor::Rgba8(convert_into(fallback, image))) + } + ColorType::Rgb16 => { + let buffer = cast_buffer(data)?; + let image = try_from_raw::<Rgb<u16>>(&buffer, width, height)?; + Ok(RgbColor::Rgba8(convert_into(fallback, image))) + } + ColorType::Rgba16 => { + let buffer = cast_buffer(data)?; + let image = try_from_raw::<Rgba<u16>>(&buffer, width, height)?; + Ok(RgbColor::Rgba8(convert_into(fallback, image))) + } + // for cases we do not support at all? + _ => Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Avif.into(), + UnsupportedErrorKind::Color(color.into()), + ), + )), + } + } +} diff --git a/vendor/image/src/codecs/avif/mod.rs b/vendor/image/src/codecs/avif/mod.rs new file mode 100644 index 0000000..f74217c --- /dev/null +++ b/vendor/image/src/codecs/avif/mod.rs @@ -0,0 +1,14 @@ +//! Encoding 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/ +#[cfg(feature = "avif-decoder")] +pub use self::decoder::AvifDecoder; +#[cfg(feature = "avif-encoder")] +pub use self::encoder::{AvifEncoder, ColorSpace}; + +#[cfg(feature = "avif-decoder")] +mod decoder; +#[cfg(feature = "avif-encoder")] +mod encoder; |