use std::borrow::Cow;
use std::io::Write;
use crate::{ColorType, ImageBuffer, ImageFormat, Pixel};
use crate::{ImageError, ImageResult};
use crate::buffer::ConvertBuffer;
use crate::color::{FromColor, Luma, LumaA, Bgr, Bgra, Rgb, Rgba};
use crate::error::{EncodingError, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind};
use bytemuck::{Pod, PodCastError, try_cast_slice, try_cast_slice_mut};
use num_traits::Zero;
use ravif::{Img, ColorSpace, Config, RGBA8, encode_rgba};
use rgb::AsPixels;
pub struct AvifEncoder<W> {
inner: W,
fallback: Vec<u8>,
}
impl<W: Write> AvifEncoder<W> {
pub fn new(w: W) -> Self {
AvifEncoder { inner: w, fallback: vec![] }
}
pub fn write_image(mut self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> {
let config = self.config(color);
let buffer = self.encode_as_img(data, width, height, color)?;
let (data, _color_size, _alpha_size) = encode_rgba(buffer, &config)
.map_err(|err| ImageError::Encoding(
EncodingError::new(ImageFormat::Avif.into(), err)
))?;
self.inner.write_all(&data)?;
Ok(())
}
fn config(&self, _color: ColorType) -> Config {
Config {
quality: 100,
alpha_quality: 100,
speed: 1,
premultiplied_alpha: false,
color_space: ColorSpace::RGB,
}
}
fn encode_as_img<'buf>(&'buf mut self, data: &'buf [u8], width: u32, height: u32, color: ColorType)
-> ImageResult<Img<&'buf [RGBA8]>>
{
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))
})
};
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();
let image: ImageBuffer<Rgba<u8>, _> = image.convert();
*buf = image.into_raw();
Img::new(buf.as_pixels(), width as usize, height as usize)
}
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) => {
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) => { Err(ImageError::Parameter(ParameterError::from_kind(
ParameterErrorKind::Generic(format!("{:?}", err))
)))
}
}
}
match color {
ColorType::Rgba8 => {
let img = try_from_raw::<Rgba<u8>>(data, width, height)?;
if img.pixels().len() == 0 {
return Err(ImageError::Parameter(ParameterError::from_kind(ParameterErrorKind::DimensionMismatch)));
}
Ok(Img::new(rgb::AsPixels::as_pixels(data), width as usize, height as usize))
},
ColorType::L8 => {
let image = try_from_raw::<Luma<u8>>(data, width, height)?;
Ok(convert_into(&mut self.fallback, image))
}
ColorType::La8 => {
let image = try_from_raw::<LumaA<u8>>(data, width, height)?;
Ok(convert_into(&mut self.fallback, image))
}
ColorType::Rgb8 => {
let image = try_from_raw::<Rgb<u8>>(data, width, height)?;
Ok(convert_into(&mut self.fallback, image))
}
ColorType::Bgr8 => {
let image = try_from_raw::<Bgr<u8>>(data, width, height)?;
Ok(convert_into(&mut self.fallback, image))
}
ColorType::Bgra8 => {
let image = try_from_raw::<Bgra<u8>>(data, width, height)?;
Ok(convert_into(&mut self.fallback, image))
}
ColorType::L16 => {
let buffer = cast_buffer(data)?;
let image = try_from_raw::<Luma<u16>>(&buffer, width, height)?;
Ok(convert_into(&mut self.fallback, image))
}
ColorType::La16 => {
let buffer = cast_buffer(data)?;
let image = try_from_raw::<LumaA<u16>>(&buffer, width, height)?;
Ok(convert_into(&mut self.fallback, image))
}
ColorType::Rgb16 => {
let buffer = cast_buffer(data)?;
let image = try_from_raw::<Rgb<u16>>(&buffer, width, height)?;
Ok(convert_into(&mut self.fallback, image))
}
ColorType::Rgba16 => {
let buffer = cast_buffer(data)?;
let image = try_from_raw::<Rgba<u16>>(&buffer, width, height)?;
Ok(convert_into(&mut self.fallback, image))
}
_ => Err(ImageError::Unsupported(UnsupportedError::from_format_and_kind(
ImageFormat::Avif.into(),
UnsupportedErrorKind::Color(color.into()),
)))
}
}
}