diff options
Diffstat (limited to 'vendor/image/src/codecs/avif/encoder.rs')
-rw-r--r-- | vendor/image/src/codecs/avif/encoder.rs | 274 |
1 files changed, 274 insertions, 0 deletions
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()), + ), + )), + } + } +} |