use crate::Speed;
use crate::avif::{
checked_buffer_size, finalize_color, finalize_with_alpha, make_av1c, validate_dims,
};
use crate::color::Cicp;
use crate::err::EncodeError;
use crate::obu::temporal_delimiter;
use crate::pixel::Pixel;
use crate::{BitDepth, ChromaFormat, EncodeConfig, isobmff};
pub struct PlanarImage<T: Pixel> {
pub width: usize,
pub height: usize,
pub bit_depth: BitDepth,
pub planes: [Vec<T>; 4],
}
fn validate_buf<T>(buf: &[T], w: usize, h: usize, ch: usize) -> Result<(), EncodeError> {
let needed = checked_buffer_size::<T>(w, h, ch)?;
if buf.len() != needed {
return Err(EncodeError::InvalidInput);
}
Ok(())
}
impl<T: Pixel> PlanarImage<T> {
pub(crate) fn validate_400(&self) -> Result<(), EncodeError> {
validate_dims(self.width as u32, self.height as u32)?;
validate_buf(&self.planes[0], self.width, self.height, 1)?;
Ok(())
}
pub(crate) fn validate_444(&self) -> Result<(), EncodeError> {
validate_dims(self.width as u32, self.height as u32)?;
validate_buf(&self.planes[0], self.width, self.height, 1)?;
validate_buf(&self.planes[1], self.width, self.height, 1)?;
validate_buf(&self.planes[2], self.width, self.height, 1)?;
Ok(())
}
pub(crate) fn validate_422(&self) -> Result<(), EncodeError> {
validate_dims(self.width as u32, self.height as u32)?;
validate_buf(&self.planes[0], self.width, self.height, 1)?;
validate_buf(&self.planes[1], self.width.div_ceil(2), self.height, 1)?;
validate_buf(&self.planes[2], self.width.div_ceil(2), self.height, 1)?;
Ok(())
}
pub(crate) fn validate_420(&self) -> Result<(), EncodeError> {
validate_dims(self.width as u32, self.height as u32)?;
validate_buf(&self.planes[0], self.width, self.height, 1)?;
validate_buf(
&self.planes[1],
self.width.div_ceil(2),
self.height.div_ceil(2),
1,
)?;
validate_buf(
&self.planes[2],
self.width.div_ceil(2),
self.height.div_ceil(2),
1,
)?;
Ok(())
}
pub(crate) fn validate_with(&self, chroma_format: ChromaFormat) -> Result<(), EncodeError> {
match chroma_format {
ChromaFormat::Yuv420 => {
self.validate_420()?;
}
ChromaFormat::Yuv422 => {
self.validate_422()?;
}
ChromaFormat::Yuv444 => {
self.validate_444()?;
}
ChromaFormat::Monochrome => {
self.validate_400()?;
}
}
Ok(())
}
}
const Q: i32 = 13;
const HALF: i32 = 1 << (Q - 1);
const Y_R: i32 = 2449; const Y_G: i32 = 4809; const Y_B: i32 = 934;
const CB_R: i32 = -1382; const CB_G: i32 = -2714; const CB_B: i32 = 4096;
const CR_R: i32 = 4096; const CR_G: i32 = -3430; const CR_B: i32 = -666;
impl<T: Pixel> PlanarImage<T> {
pub fn from_interleaved_rgb(
width: usize,
height: usize,
bit_depth: BitDepth,
rgb: &[T],
) -> Result<Self, EncodeError> {
if rgb.len() != width * height * 3 {
return Err(EncodeError::InvalidDimensions {
width: width as u32,
height: height as u32,
});
}
let n = width * height;
let mut g = vec![T::default(); n];
let mut b = vec![T::default(); n];
let mut r = vec![T::default(); n];
for (((px, g), b), r) in rgb
.as_chunks::<3>()
.0
.iter()
.zip(g.iter_mut())
.zip(b.iter_mut())
.zip(r.iter_mut())
{
*r = px[0];
*g = px[1];
*b = px[2];
}
Ok(PlanarImage {
width,
height,
bit_depth,
planes: [g, b, r, Vec::new()],
})
}
pub fn from_interleaved_rgba(
width: usize,
height: usize,
bit_depth: BitDepth,
rgba: &[T],
) -> Result<Self, EncodeError> {
if rgba.len() != width * height * 4 {
return Err(EncodeError::InvalidDimensions {
width: width as u32,
height: height as u32,
});
}
let n = width * height;
let mut g = vec![T::default(); n];
let mut b = vec![T::default(); n];
let mut r = vec![T::default(); n];
let mut a = vec![T::default(); n];
for ((((px, g), b), r), a) in rgba
.as_chunks::<4>()
.0
.iter()
.zip(g.iter_mut())
.zip(b.iter_mut())
.zip(r.iter_mut())
.zip(a.iter_mut())
{
*r = px[0];
*g = px[1];
*b = px[2];
*a = px[3];
}
Ok(PlanarImage {
width,
height,
bit_depth,
planes: [g, b, r, a],
})
}
pub fn from_luma(
width: usize,
height: usize,
bit_depth: BitDepth,
luma: &[T],
) -> Result<Self, EncodeError> {
if luma.len() != width * height {
return Err(EncodeError::InvalidDimensions {
width: width as u32,
height: height as u32,
});
}
Ok(PlanarImage {
width,
height,
bit_depth,
planes: [luma.to_vec(), Vec::new(), Vec::new(), Vec::new()],
})
}
pub fn from_interleaved_gray_alpha(
width: usize,
height: usize,
bit_depth: BitDepth,
gray_alpha: &[T],
) -> Result<Self, EncodeError> {
if gray_alpha.len() != width * height * 2 {
return Err(EncodeError::InvalidDimensions {
width: width as u32,
height: height as u32,
});
}
let n = width * height;
let mut luma = vec![T::default(); n];
let mut a = vec![T::default(); n];
for ((px, luma), a) in gray_alpha
.as_chunks::<2>()
.0
.iter()
.zip(luma.iter_mut())
.zip(a.iter_mut())
{
*luma = px[0];
*a = px[1];
}
Ok(PlanarImage {
width,
height,
bit_depth,
planes: [luma, a, Vec::new(), Vec::new()],
})
}
pub(crate) fn packed_3(&self) -> PlanarImage<T> {
PlanarImage {
width: self.width,
height: self.height,
bit_depth: self.bit_depth,
planes: [
self.planes[0].to_vec(),
self.planes[1].to_vec(),
self.planes[2].to_vec(),
vec![],
],
}
}
pub(crate) fn packed_alpha_4(&self) -> PlanarImage<T> {
PlanarImage {
width: self.width,
height: self.height,
bit_depth: self.bit_depth,
planes: [self.planes[3].to_vec(), vec![], vec![], vec![]],
}
}
pub(crate) fn packed_alpha_2(&self) -> PlanarImage<T> {
PlanarImage {
width: self.width,
height: self.height,
bit_depth: self.bit_depth,
planes: [self.planes[1].to_vec(), vec![], vec![], vec![]],
}
}
pub(crate) fn packed_1(&self) -> PlanarImage<T> {
PlanarImage {
width: self.width,
height: self.height,
bit_depth: self.bit_depth,
planes: [self.planes[0].to_vec(), vec![], vec![], vec![]],
}
}
pub fn to_interleaved_rgb(&self) -> Vec<T> {
let n = self.width * self.height;
let mut out = vec![T::default(); n * 3];
for (((dst, &r), &g), &b) in out
.as_chunks_mut::<3>()
.0
.iter_mut()
.zip(self.planes[2].iter())
.zip(self.planes[0].iter())
.zip(self.planes[1].iter())
{
dst[0] = r;
dst[1] = g;
dst[2] = b;
}
out
}
}
#[allow(clippy::too_many_arguments)]
pub fn encode_still_lossy<T: Pixel>(
img: &PlanarImage<T>,
base_q_idx: u8,
color: Option<&Cicp>,
threads: usize,
speed: Speed,
aq: bool,
vb: crate::av1_coder::VarianceBoost,
cdef: bool,
wiener: bool,
) -> Vec<u8> {
assert!(
img.width > 0 && img.height > 0,
"width/height must be non-zero"
);
assert!(base_q_idx != 0, "use encode_still for lossless (q=0)");
let bd = img.bit_depth;
let maxv = (1i32 << bd.bits()) - 1;
let off = (1i32 << (bd.bits() - 1)) as f32;
let mx = maxv as f32;
let n = img.planes[0].len();
let off_q = (off as i32) << Q;
let mx_i = mx as i32;
let (mut y, mut cb, mut cr) = (vec![0i32; n], vec![0i32; n], vec![0i32; n]);
for (((((yv, cbv), crv), &rr), &gg), &bb) in y
.iter_mut()
.zip(cb.iter_mut())
.zip(cr.iter_mut())
.zip(img.planes[2].iter())
.zip(img.planes[0].iter())
.zip(img.planes[1].iter())
{
let (ri, gi, bi) = (rr.to_i32(), gg.to_i32(), bb.to_i32());
*yv = ((Y_R * ri + Y_G * gi + Y_B * bi + HALF) >> Q).clamp(0, mx_i);
*cbv = ((CB_R * ri + CB_G * gi + CB_B * bi + off_q + HALF) >> Q).clamp(0, mx_i);
*crv = ((CR_R * ri + CR_G * gi + CR_B * bi + off_q + HALF) >> Q).clamp(0, mx_i);
}
crate::av1_coder::encode_av1_lossy_image_cs(
base_q_idx,
bd.bits(),
img.width,
img.height,
&y,
&cb,
&cr,
color,
threads,
speed,
aq,
vb,
cdef,
wiener,
)
}
#[allow(clippy::too_many_arguments)]
pub fn encode_still_lossy_422<T: Pixel>(
img: &PlanarImage<T>,
base_q_idx: u8,
color: Option<&Cicp>,
threads: usize,
speed: Speed,
aq: bool,
vb: crate::av1_coder::VarianceBoost,
cdef: bool,
wiener: bool,
) -> Vec<u8> {
assert!(
img.width > 0 && img.height > 0,
"width/height must be non-zero"
);
assert!(base_q_idx != 0, "use encode_still for lossless (q=0)");
let (w, h) = (img.width, img.height);
let bd = img.bit_depth;
let maxv = (1i32 << bd.bits()) - 1;
let off = (1i32 << (bd.bits() - 1)) as f32;
let mx = maxv as f32;
let cw = w.div_ceil(2);
let mut y = vec![0i32; w * h];
let off_q = (off as i32) << Q;
let mx_i = mx as i32;
let mut fcb_q = vec![0i32; w * h];
let mut fcr_q = vec![0i32; w * h];
for (((((yv, fcbv), fcrv), &rr), &gg), &bb) in y
.iter_mut()
.zip(fcb_q.iter_mut())
.zip(fcr_q.iter_mut())
.zip(img.planes[2].iter())
.zip(img.planes[0].iter())
.zip(img.planes[1].iter())
{
let (ri, gi, bi) = (rr.to_i32(), gg.to_i32(), bb.to_i32());
*yv = ((Y_R * ri + Y_G * gi + Y_B * bi + HALF) >> Q).clamp(0, mx_i);
*fcbv = CB_R * ri + CB_G * gi + CB_B * bi + off_q;
*fcrv = CR_R * ri + CR_G * gi + CR_B * bi + off_q;
}
const HALF_AVG: i32 = 1 << Q;
let (mut cb, mut cr) = (vec![0i32; cw * h], vec![0i32; cw * h]);
for row in 0..h {
for c in 0..cw {
let x0 = 2 * c;
let x1 = (2 * c + 1).min(w - 1);
let cb0 = fcb_q[row * w + x0];
let cb1 = fcb_q[row * w + x1];
let cr0 = fcr_q[row * w + x0];
let cr1 = fcr_q[row * w + x1];
cb[row * cw + c] = ((cb0 + cb1 + HALF_AVG) >> (Q + 1)).clamp(0, mx_i);
cr[row * cw + c] = ((cr0 + cr1 + HALF_AVG) >> (Q + 1)).clamp(0, mx_i);
}
}
crate::av1_coder::encode_av1_lossy_image_422(
base_q_idx,
bd.bits(),
w,
h,
&y,
&cb,
&cr,
color,
threads,
speed,
aq,
vb,
cdef,
wiener,
)
}
#[allow(clippy::too_many_arguments)]
pub fn encode_still_lossy_420<T: Pixel>(
img: &PlanarImage<T>,
base_q_idx: u8,
color: Option<&Cicp>,
threads: usize,
speed: Speed,
aq: bool,
vb: crate::av1_coder::VarianceBoost,
cdef: bool,
wiener: bool,
) -> Vec<u8> {
assert!(
img.width > 0 && img.height > 0,
"width/height must be non-zero"
);
assert!(base_q_idx != 0, "use encode_still for lossless (q=0)");
let (w, h) = (img.width, img.height);
let bd = img.bit_depth;
let maxv = (1i32 << bd.bits()) - 1;
let off = (1i32 << (bd.bits() - 1)) as f32;
let mx = maxv as f32;
let (cw, ch) = (w.div_ceil(2), h.div_ceil(2));
let off_q = (off as i32) << Q;
let mx_i = mx as i32;
let mut y = vec![0i32; w * h];
let mut fcb_q = vec![0i32; w * h];
let mut fcr_q = vec![0i32; w * h];
for (((((yv, fcbv), fcrv), &rr), &gg), &bb) in y
.iter_mut()
.zip(fcb_q.iter_mut())
.zip(fcr_q.iter_mut())
.zip(img.planes[2].iter())
.zip(img.planes[0].iter())
.zip(img.planes[1].iter())
{
let (ri, gi, bi) = (rr.to_i32(), gg.to_i32(), bb.to_i32());
*yv = ((Y_R * ri + Y_G * gi + Y_B * bi + HALF) >> Q).clamp(0, mx_i);
*fcbv = CB_R * ri + CB_G * gi + CB_B * bi + off_q;
*fcrv = CR_R * ri + CR_G * gi + CR_B * bi + off_q;
}
const HALF_AVG: i32 = 1 << (Q + 1);
let (mut cb, mut cr) = (vec![0i32; cw * ch], vec![0i32; cw * ch]);
for row in 0..ch {
let cb_r = &mut cb[row * cw..row * cw + cw];
let cr_r = &mut cr[row * cw..row * cw + cw];
for (c, (cb, cr)) in cb_r.iter_mut().zip(cr_r.iter_mut()).enumerate() {
let (x0, x1) = (2 * c, (2 * c + 1).min(w - 1));
let (y0, y1) = (2 * row, (2 * row + 1).min(h - 1));
let avg_q =
|f: &[i32]| f[y0 * w + x0] + f[y0 * w + x1] + f[y1 * w + x0] + f[y1 * w + x1];
*cb = ((avg_q(&fcb_q) + HALF_AVG) >> (Q + 2)).clamp(0, mx_i);
*cr = ((avg_q(&fcr_q) + HALF_AVG) >> (Q + 2)).clamp(0, mx_i);
}
}
crate::av1_coder::encode_av1_lossy_image_420(
base_q_idx,
bd.bits(),
w,
h,
&y,
&cb,
&cr,
color,
threads,
speed,
aq,
vb,
cdef,
wiener,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_lossy_gray_obu<T: Pixel>(
img: &PlanarImage<T>,
bit_depth: BitDepth,
base_q_idx: u8,
full_range: bool,
threads: usize,
speed: Speed,
aq: bool,
vb: crate::av1_coder::VarianceBoost,
cdef: bool,
wiener: bool,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(img.width as u32, img.height as u32)?;
img.validate_400()?;
let maxv = (1i32 << bit_depth.bits()) - 1;
if base_q_idx == 0 {
let luma: Vec<i16> = img.planes[0]
.iter()
.map(|v| v.to_i32().clamp(0, maxv) as i16)
.collect();
return Ok(crate::av1_coder::encode_av1_mono_lossless_image(
bit_depth.bits(),
img.width,
img.height,
&luma,
full_range,
threads,
));
}
let luma: Vec<i32> = img.planes[0]
.iter()
.map(|v| v.to_i32().clamp(0, maxv))
.collect();
let bytes = crate::av1_coder::encode_av1_mono_image(
base_q_idx,
bit_depth.bits(),
img.width,
img.height,
&luma,
full_range,
threads,
speed,
aq,
vb,
cdef,
wiener,
);
Ok(bytes)
}
pub fn encode_lossless_gray_obu<T: Pixel>(
img: &PlanarImage<T>,
full_range: bool,
threads: usize,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(img.width as u32, img.height as u32)?;
img.validate_400()?;
encode_lossy_gray_obu(
img,
img.bit_depth,
0,
full_range,
threads,
Speed::Slow,
false,
crate::av1_coder::VarianceBoost::off(),
false,
false,
)
}
pub fn encode_lossless_gray<T: Pixel>(
img: &PlanarImage<T>,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(img.width as u32, img.height as u32)?;
img.validate_400()?;
let obu = encode_lossy_gray_obu(
img,
img.bit_depth,
0,
true,
cfg.threads,
Speed::Slow,
false,
crate::av1_coder::VarianceBoost::off(),
false,
false,
)?;
finalize_color(
obu,
img.width as u32,
img.height as u32,
img.bit_depth.bits(),
ChromaFormat::Monochrome,
cfg,
)
}
pub fn encode_lossless_gray_alpha<T: Pixel>(
img: &PlanarImage<T>,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(img.width as u32, img.height as u32)?;
cfg.validate()?;
crate::avif::validate_buf(&img.planes[0], img.width as u32, img.height as u32, 1)?;
crate::avif::validate_buf(&img.planes[1], img.width as u32, img.height as u32, 1)?;
if cfg.chroma != ChromaFormat::Monochrome {
return Err(EncodeError::UnsupportedChromaFormat(cfg.chroma));
}
let luma_obu = encode_lossless_gray(&img.packed_1(), cfg)?;
let alpha_obu = encode_lossless_gray(&img.packed_alpha_2(), cfg)?;
finalize_with_alpha(
luma_obu,
alpha_obu,
img.width as u32,
img.height as u32,
img.bit_depth.bits(),
ChromaFormat::Monochrome,
cfg,
)
}
pub fn encode_lossless_obu<T: Pixel>(
img: &PlanarImage<T>,
color: Option<&Cicp>,
threads: usize,
) -> Result<Vec<u8>, EncodeError> {
img.validate_444()?;
let profile: u32 = if img.bit_depth == BitDepth::Twelve {
2
} else {
1
};
let (w, h) = (img.width, img.height);
let (w8, h8) = (crate::av1_coder::align8(w), crate::av1_coder::align8(h));
let to_i16 = |p: &[T]| p.iter().map(|p| p.to_i32() as i16).collect::<Vec<i16>>();
let planes_i16: [Vec<i16>; 3] = [
crate::av1_coder::pad_to_mult8(&to_i16(&img.planes[0]), w, h, w8, h8),
crate::av1_coder::pad_to_mult8(&to_i16(&img.planes[1]), w, h, w8, h8),
crate::av1_coder::pad_to_mult8(&to_i16(&img.planes[2]), w, h, w8, h8),
];
let mut bytes = Vec::new();
bytes.extend_from_slice(&temporal_delimiter());
bytes.extend_from_slice(&crate::obu::sequence_header_cicp(
w as u32,
h as u32,
profile,
img.bit_depth.bits(),
color,
));
bytes.extend_from_slice(&crate::av1_coder::encode_lossless_frame_obus(
img.bit_depth.bits(),
w8,
h8,
&planes_i16,
threads,
));
Ok(bytes)
}
pub fn encode_lossless<T: Pixel>(
img: &PlanarImage<T>,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
img.validate_444()?;
if cfg.chroma != ChromaFormat::Yuv444 {
return Err(EncodeError::UnsupportedChromaFormat(cfg.chroma));
}
let obu = encode_lossless_obu(img, cfg.color_encoding.as_ref(), cfg.threads)?;
let av1c = make_av1c(
&obu,
img.bit_depth.bits(),
img.width as u32,
img.height as u32,
ChromaFormat::Yuv444,
);
isobmff::wrap_av1_image(
&obu,
img.width as u32,
img.height as u32,
img.bit_depth.bits(),
3,
&av1c,
cfg.color_encoding.as_ref(),
cfg.icc.as_deref(),
&cfg.metadata,
)
}
pub fn encode_lossless_with_alpha<T: Pixel + Copy>(
img: &PlanarImage<T>,
cfg: &EncodeConfig,
) -> Result<Vec<u8>, EncodeError> {
validate_dims(img.width as u32, img.height as u32)?;
cfg.validate()?;
crate::avif::validate_buf(&img.planes[0], img.width as u32, img.height as u32, 1)?;
crate::avif::validate_buf(&img.planes[1], img.width as u32, img.height as u32, 1)?;
crate::avif::validate_buf(&img.planes[2], img.width as u32, img.height as u32, 1)?;
crate::avif::validate_buf(&img.planes[3], img.width as u32, img.height as u32, 1)?;
if cfg.chroma != ChromaFormat::Yuv444 {
return Err(EncodeError::UnsupportedChromaFormat(cfg.chroma));
}
let obu = encode_lossless_obu(&img.packed_3(), cfg.color_encoding.as_ref(), cfg.threads)?;
let alpha_obu = encode_lossy_gray_obu(
&img.packed_alpha_4(),
img.bit_depth,
0,
true,
cfg.threads,
Speed::Slow,
false,
crate::av1_coder::VarianceBoost::off(),
false,
false,
)?;
finalize_with_alpha(
obu,
alpha_obu,
img.width as u32,
img.height as u32,
img.bit_depth.bits(),
cfg.chroma,
cfg,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_yuv444_obu<T: Pixel>(
planar_image: &PlanarImage<T>,
bit_depth: BitDepth,
base_q_idx: u8,
color: Option<&Cicp>,
threads: usize,
speed: Speed,
aq: bool,
vb: crate::av1_coder::VarianceBoost,
cdef: bool,
wiener: bool,
) -> Result<Vec<u8>, EncodeError> {
planar_image.validate_444()?;
assert_ne!(base_q_idx, 0, "use encode_still for lossless");
let maxv = (1i32 << bit_depth.bits()) - 1;
let to_i = |p: &[T]| {
p.iter()
.map(|v| v.to_i32().clamp(0, maxv))
.collect::<Vec<i32>>()
};
let bytes = crate::av1_coder::encode_av1_lossy_image_cs(
base_q_idx,
bit_depth.bits(),
planar_image.width,
planar_image.height,
&to_i(&planar_image.planes[0]),
&to_i(&planar_image.planes[1]),
&to_i(&planar_image.planes[2]),
color,
threads,
speed,
aq,
vb,
cdef,
wiener,
);
Ok(bytes)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_yuv422_obu<T: Pixel>(
planar_image: &PlanarImage<T>,
bit_depth: BitDepth,
base_q_idx: u8,
color: Option<&Cicp>,
threads: usize,
speed: Speed,
aq: bool,
vb: crate::av1_coder::VarianceBoost,
cdef: bool,
wiener: bool,
) -> Result<Vec<u8>, EncodeError> {
planar_image.validate_422()?;
assert!(base_q_idx != 0, "4:2:2 doesn't support lossless encoding");
let maxv = (1i32 << bit_depth.bits()) - 1;
let to_i = |p: &[T]| {
p.iter()
.map(|v| v.to_i32().clamp(0, maxv))
.collect::<Vec<i32>>()
};
let bytes = crate::av1_coder::encode_av1_lossy_image_422(
base_q_idx,
bit_depth.bits(),
planar_image.width,
planar_image.height,
&to_i(&planar_image.planes[0]),
&to_i(&planar_image.planes[1]),
&to_i(&planar_image.planes[2]),
color,
threads,
speed,
aq,
vb,
cdef,
wiener,
);
Ok(bytes)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_yuv420_obu<T: Pixel>(
planar_image: &PlanarImage<T>,
bit_depth: BitDepth,
base_q_idx: u8,
color: Option<&Cicp>,
threads: usize,
speed: Speed,
aq: bool,
vb: crate::av1_coder::VarianceBoost,
cdef: bool,
wiener: bool,
) -> Result<Vec<u8>, EncodeError> {
planar_image.validate_420()?;
assert!(base_q_idx != 0, "use encode_still for lossless");
let maxv = (1i32 << bit_depth.bits()) - 1;
let to_i = |p: &[T]| {
p.iter()
.map(|v| v.to_i32().clamp(0, maxv))
.collect::<Vec<i32>>()
};
let bytes = crate::av1_coder::encode_av1_lossy_image_420(
base_q_idx,
bit_depth.bits(),
planar_image.width,
planar_image.height,
&to_i(&planar_image.planes[0]),
&to_i(&planar_image.planes[1]),
&to_i(&planar_image.planes[2]),
color,
threads,
speed,
aq,
vb,
cdef,
wiener,
);
Ok(bytes)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::av1_coder::VarianceBoost;
#[test]
fn arbitrary_sizes_do_not_panic() {
for &(w, h) in &[(1usize, 1usize), (17, 17), (65, 33), (127, 129), (33, 7)] {
let rgb = vec![100u8; w * h * 3];
let img = PlanarImage::from_interleaved_rgb(w, h, BitDepth::Twelve, &rgb).unwrap();
assert!(!encode_lossless_obu(&img, None, 9).unwrap().is_empty());
assert!(
!encode_still_lossy(
&img,
16,
Some(&Cicp::srgb_ycbcr()),
0,
Speed::Slow,
false,
VarianceBoost::off(),
true,
false
)
.is_empty()
);
}
}
}